diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 502ebe3b9..9194929f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,9 @@ on: required: true type: string +env: + AGENTCORE_TELEMETRY_DISABLED: "1" + jobs: build: name: Build (${{ matrix.name }}) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index f768f80e6..40e81b6df 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -10,6 +10,9 @@ on: CODECOV_TOKEN: required: false +env: + AGENTCORE_TELEMETRY_DISABLED: "1" + jobs: test: name: Test (${{ matrix.name }}) diff --git a/src/telemetry/client.test.tsx b/src/telemetry/client.test.tsx index bad75db7b..a2fca90bf 100644 --- a/src/telemetry/client.test.tsx +++ b/src/telemetry/client.test.tsx @@ -287,10 +287,13 @@ describe("DefaultTelemetryClient", () => { describe("OtelHistogramSink", () => { let testCollector: ReturnType; let receivedBodies: any[]; + let savedTelemetryDisabled: string | undefined; const logger = createSilentLogger(); beforeEach(async () => { + savedTelemetryDisabled = process.env.AGENTCORE_TELEMETRY_DISABLED; + delete process.env.AGENTCORE_TELEMETRY_DISABLED; receivedBodies = []; testCollector = Bun.serve({ port: 0, @@ -303,6 +306,11 @@ describe("OtelHistogramSink", () => { }); afterEach(async () => { + if (savedTelemetryDisabled === undefined) { + delete process.env.AGENTCORE_TELEMETRY_DISABLED; + } else { + process.env.AGENTCORE_TELEMETRY_DISABLED = savedTelemetryDisabled; + } testCollector.stop(true); }); @@ -388,4 +396,40 @@ describe("OtelHistogramSink", () => { } }, ); + + test("AGENTCORE_TELEMETRY_DISABLED suppresses network export while audit still writes", async () => { + const auditFilePath = join(tmpdir(), `env-disabled-audit-${crypto.randomUUID()}.jsonl`); + const globalConfigAccessor = new TestGlobalConfigAccessor({ + initialConfigData: { + ...DEFAULT_GLOBAL_CONFIG, + telemetry: { + enabled: true, + audit: true, + endpoint: `http://localhost:${testCollector.port}`, + }, + }, + }); + + process.env.AGENTCORE_TELEMETRY_DISABLED = "1"; + try { + const client = new DefaultTelemetryClient({ + logger, + sessionId: "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", + globalConfigAccessor, + auditFilePath, + }); + const event = client.createMetricEvent("cli.command_run", { + exit_reason: "success", + command_path: "/agentcore", + }); + await event.emit(100); + await client.shutdown(); + } finally { + delete process.env.AGENTCORE_TELEMETRY_DISABLED; + } + + expect(receivedBodies).toHaveLength(0); + expect(await readFile(auditFilePath, "utf8")).toContain("cli.command_run"); + await rm(auditFilePath, { force: true }); + }); }); diff --git a/src/telemetry/client.tsx b/src/telemetry/client.tsx index b1d7e0381..ce7b78ed2 100644 --- a/src/telemetry/client.tsx +++ b/src/telemetry/client.tsx @@ -87,7 +87,7 @@ export class DefaultTelemetryClient implements TelemetryClient { }), ); - if (globalConfig.telemetry.enabled) + if (globalConfig.telemetry.enabled && !telemetryDisabledByEnv()) metricSinks.push( new OtelHistogramSink({ logger: this.logger.child({ module: "otelCollectorSink" }), @@ -114,6 +114,12 @@ export class DefaultTelemetryClient implements TelemetryClient { }); } +/** Returns true when AGENTCORE_TELEMETRY_DISABLED is set to "true" or "1". */ +function telemetryDisabledByEnv(): boolean { + const value = process.env.AGENTCORE_TELEMETRY_DISABLED?.toLowerCase().trim(); + return value === "true" || value === "1"; +} + /** wraps an async function such that it only executes once **/ function once(fn: () => Promise): () => Promise { let cachedPromise: Promise | undefined;