diff --git a/packages/opencode/src/altimate/bridge/client.ts b/packages/opencode/src/altimate/bridge/client.ts index 4ea1b3ccbd..ddfc790373 100644 --- a/packages/opencode/src/altimate/bridge/client.ts +++ b/packages/opencode/src/altimate/bridge/client.ts @@ -132,8 +132,19 @@ export namespace Bridge { async function start() { await ensureEngine() const pythonCmd = resolvePython() + + // Propagate altimate-code's telemetry opt-out to the Python engine. + // The engine calls altimate_core.init() lazily; this env var ensures + // it won't send telemetry when the user has disabled it here. + await Telemetry.init() + const childEnv = { ...process.env } + if (!Telemetry.isEnabled()) { + childEnv.ALTIMATE_TELEMETRY_DISABLED = "true" + } + child = spawn(pythonCmd, ["-m", "altimate_engine.server"], { stdio: ["pipe", "pipe", "pipe"], + env: childEnv, }) buffer = "" diff --git a/packages/opencode/src/altimate/telemetry/index.ts b/packages/opencode/src/altimate/telemetry/index.ts index 9c501f2664..4106d1c44e 100644 --- a/packages/opencode/src/altimate/telemetry/index.ts +++ b/packages/opencode/src/altimate/telemetry/index.ts @@ -455,6 +455,11 @@ export namespace Telemetry { return { sessionId, projectId } } + /** Returns true only after init() has completed and telemetry is enabled. */ + export function isEnabled(): boolean { + return initDone && enabled + } + export function track(event: Event) { // Before init completes: buffer (flushed once init enables, or cleared if disabled). // After init completed and disabled telemetry: drop silently. diff --git a/packages/opencode/test/bridge/client.test.ts b/packages/opencode/test/bridge/client.test.ts index c9103e2fa9..d8f9c44adf 100644 --- a/packages/opencode/test/bridge/client.test.ts +++ b/packages/opencode/test/bridge/client.test.ts @@ -490,6 +490,56 @@ describe("engine.ts extras tracking", () => { }) }) +// --------------------------------------------------------------------------- +// Tests — telemetry opt-out env var propagation +// --------------------------------------------------------------------------- + +describe("Bridge telemetry opt-out propagation", () => { + test("source code injects ALTIMATE_TELEMETRY_DISABLED before spawning child process", async () => { + const clientSrc = path.resolve( + __dirname, + "../../src/altimate/bridge/client.ts", + ) + const source = await fsp.readFile(clientSrc, "utf-8") + + // Must await Telemetry.init() before spawning so opt-out state is known + expect(source).toContain("await Telemetry.init()") + // Must inject the env var when telemetry is disabled + expect(source).toContain("ALTIMATE_TELEMETRY_DISABLED") + expect(source).toContain('"true"') + // Must use a copy of process.env (not mutate the parent env) + expect(source).toContain("{ ...process.env }") + }) + + test("source code checks Telemetry.isEnabled() to gate env var injection", async () => { + const clientSrc = path.resolve( + __dirname, + "../../src/altimate/bridge/client.ts", + ) + const source = await fsp.readFile(clientSrc, "utf-8") + + expect(source).toContain("Telemetry.isEnabled()") + // Env var must only be set when telemetry is NOT enabled + const lines = source.split("\n") + const injectionLine = lines.findIndex(l => l.includes("ALTIMATE_TELEMETRY_DISABLED") && l.includes('"true"')) + expect(injectionLine).toBeGreaterThan(0) + // The line injecting the var must be inside an if (!Telemetry.isEnabled()) block + const surroundingBlock = lines.slice(Math.max(0, injectionLine - 5), injectionLine + 1).join("\n") + expect(surroundingBlock).toContain("isEnabled()") + }) + + test("source code passes childEnv to spawn (not process.env directly)", async () => { + const clientSrc = path.resolve( + __dirname, + "../../src/altimate/bridge/client.ts", + ) + const source = await fsp.readFile(clientSrc, "utf-8") + + // spawn() must use childEnv, not the raw process.env + expect(source).toContain("env: childEnv") + }) +}) + // --------------------------------------------------------------------------- // Tests — engine.ts ensureEngineImpl validation logic // --------------------------------------------------------------------------- diff --git a/packages/opencode/test/telemetry/telemetry.test.ts b/packages/opencode/test/telemetry/telemetry.test.ts index e056fef1b2..ef905a9b62 100644 --- a/packages/opencode/test/telemetry/telemetry.test.ts +++ b/packages/opencode/test/telemetry/telemetry.test.ts @@ -1439,3 +1439,67 @@ describe("telemetry.memory", () => { }).not.toThrow() }) }) + +// --------------------------------------------------------------------------- +// 14. Telemetry.isEnabled() state machine +// --------------------------------------------------------------------------- +describe("Telemetry.isEnabled()", () => { + afterEach(async () => { + await Telemetry.shutdown() + mock.restore() + }) + + test("returns false before init() is called", () => { + expect(Telemetry.isEnabled()).toBe(false) + }) + + test("returns false after init() when ALTIMATE_TELEMETRY_DISABLED=true", async () => { + const orig = process.env.ALTIMATE_TELEMETRY_DISABLED + try { + process.env.ALTIMATE_TELEMETRY_DISABLED = "true" + await Telemetry.init() + expect(Telemetry.isEnabled()).toBe(false) + } finally { + if (orig !== undefined) process.env.ALTIMATE_TELEMETRY_DISABLED = orig + else delete process.env.ALTIMATE_TELEMETRY_DISABLED + } + }) + + test("returns true after init() when telemetry is enabled", async () => { + const origEnv = process.env.ALTIMATE_TELEMETRY_DISABLED + const origCs = process.env.APPLICATIONINSIGHTS_CONNECTION_STRING + spyOn(global, "fetch").mockImplementation(async () => new Response("", { status: 200 })) + try { + delete process.env.ALTIMATE_TELEMETRY_DISABLED + process.env.APPLICATIONINSIGHTS_CONNECTION_STRING = + "InstrumentationKey=k;IngestionEndpoint=https://e.com" + await Telemetry.init() + expect(Telemetry.isEnabled()).toBe(true) + } finally { + if (origEnv !== undefined) process.env.ALTIMATE_TELEMETRY_DISABLED = origEnv + else delete process.env.ALTIMATE_TELEMETRY_DISABLED + if (origCs !== undefined) process.env.APPLICATIONINSIGHTS_CONNECTION_STRING = origCs + else delete process.env.APPLICATIONINSIGHTS_CONNECTION_STRING + } + }) + + test("returns false after shutdown()", async () => { + const origEnv = process.env.ALTIMATE_TELEMETRY_DISABLED + const origCs = process.env.APPLICATIONINSIGHTS_CONNECTION_STRING + spyOn(global, "fetch").mockImplementation(async () => new Response("", { status: 200 })) + try { + delete process.env.ALTIMATE_TELEMETRY_DISABLED + process.env.APPLICATIONINSIGHTS_CONNECTION_STRING = + "InstrumentationKey=k;IngestionEndpoint=https://e.com" + await Telemetry.init() + expect(Telemetry.isEnabled()).toBe(true) + await Telemetry.shutdown() + expect(Telemetry.isEnabled()).toBe(false) + } finally { + if (origEnv !== undefined) process.env.ALTIMATE_TELEMETRY_DISABLED = origEnv + else delete process.env.ALTIMATE_TELEMETRY_DISABLED + if (origCs !== undefined) process.env.APPLICATIONINSIGHTS_CONNECTION_STRING = origCs + else delete process.env.APPLICATIONINSIGHTS_CONNECTION_STRING + } + }) +})