diff --git a/src/services/analytics/config.ts b/src/services/analytics/config.ts index 9e80601..524242a 100644 --- a/src/services/analytics/config.ts +++ b/src/services/analytics/config.ts @@ -2,7 +2,7 @@ * Shared analytics configuration * * Common logic for determining when analytics should be disabled - * across all analytics systems (Datadog, 1P) + * across the remaining local analytics compatibility surfaces. */ import { isEnvTruthy } from '../../utils/envUtils.js' @@ -31,7 +31,7 @@ export function isAnalyticsDisabled(): boolean { * * Unlike isAnalyticsDisabled(), this does NOT block on 3P providers * (Bedrock/Vertex/Foundry). The survey is a local UI prompt with no - * transcript data — enterprise customers capture responses via OTEL. + * transcript upload in this fork. */ export function isFeedbackSurveyDisabled(): boolean { return process.env.NODE_ENV === 'test' || isTelemetryDisabled() diff --git a/src/services/analytics/index.ts b/src/services/analytics/index.ts index c827e74..ec1ff51 100644 --- a/src/services/analytics/index.ts +++ b/src/services/analytics/index.ts @@ -1,11 +1,9 @@ /** * Analytics service - public API for event logging * - * This module serves as the main entry point for analytics events in Claude CLI. - * - * DESIGN: This module has NO dependencies to avoid import cycles. - * Events are queued until attachAnalyticsSink() is called during app initialization. - * The sink handles routing to Datadog and 1P event logging. + * The open build intentionally ships without product telemetry. We keep this + * module as a compatibility boundary so existing call sites can remain + * unchanged while all analytics become inert. */ /** @@ -27,15 +25,14 @@ export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never */ export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never -// Internal type for logEvent metadata in the local no-op sink. -type LogEventMetadata = { [key: string]: boolean | number | undefined } - -type QueuedEvent = { - eventName: string - metadata: LogEventMetadata - async: boolean +export function stripProtoFields( + metadata: Record, +): Record { + return metadata } +type LogEventMetadata = { [key: string]: boolean | number | undefined } + /** * Sink interface for the analytics backend */ @@ -47,97 +44,26 @@ export type AnalyticsSink = { ) => Promise } -// Event queue for events logged before sink is attached -const eventQueue: QueuedEvent[] = [] - -// Sink - initialized during app startup -let sink: AnalyticsSink | null = null - -/** - * Attach the analytics sink that will receive all events. - * Queued events are drained asynchronously via queueMicrotask to avoid - * adding latency to the startup path. - * - * Idempotent: if a sink is already attached, this is a no-op. This allows - * calling from both the preAction hook (for subcommands) and setup() (for - * the default command) without coordination. - */ -export function attachAnalyticsSink(newSink: AnalyticsSink): void { - if (sink !== null) { - return - } - sink = newSink - - // Drain the queue asynchronously to avoid blocking startup - if (eventQueue.length > 0) { - const queuedEvents = [...eventQueue] - eventQueue.length = 0 - - // Log queue size for ants to help debug analytics initialization timing - if (process.env.USER_TYPE === 'ant') { - sink.logEvent('analytics_sink_attached', { - queued_event_count: queuedEvents.length, - }) - } - - queueMicrotask(() => { - for (const event of queuedEvents) { - if (event.async) { - void sink!.logEventAsync(event.eventName, event.metadata) - } else { - sink!.logEvent(event.eventName, event.metadata) - } - } - }) - } -} +export function attachAnalyticsSink(_newSink: AnalyticsSink): void {} /** * Log an event to analytics backends (synchronous) - * - * Events may be sampled based on the 'tengu_event_sampling_config' dynamic config. - * When sampled, the sample_rate is added to the event metadata. - * - * If no sink is attached, events are queued and drained when the sink attaches. */ export function logEvent( - eventName: string, - // intentionally no strings unless AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, - // to avoid accidentally logging code/filepaths - metadata: LogEventMetadata, -): void { - if (sink === null) { - eventQueue.push({ eventName, metadata, async: false }) - return - } - sink.logEvent(eventName, metadata) -} + _eventName: string, + _metadata: LogEventMetadata, +): void {} /** * Log an event to analytics backends (asynchronous) - * - * Events may be sampled based on the 'tengu_event_sampling_config' dynamic config. - * When sampled, the sample_rate is added to the event metadata. - * - * If no sink is attached, events are queued and drained when the sink attaches. */ export async function logEventAsync( - eventName: string, - // intentionally no strings, to avoid accidentally logging code/filepaths - metadata: LogEventMetadata, -): Promise { - if (sink === null) { - eventQueue.push({ eventName, metadata, async: true }) - return - } - await sink.logEventAsync(eventName, metadata) -} + _eventName: string, + _metadata: LogEventMetadata, +): Promise {} /** * Reset analytics state for testing purposes only. * @internal */ -export function _resetForTesting(): void { - sink = null - eventQueue.length = 0 -} +export function _resetForTesting(): void {} diff --git a/src/services/analytics/sink.ts b/src/services/analytics/sink.ts index fe6c29f..0d54a6b 100644 --- a/src/services/analytics/sink.ts +++ b/src/services/analytics/sink.ts @@ -1,31 +1,10 @@ /** * Analytics sink implementation * - * This open build keeps the analytics sink boundary for compatibility, but - * drops all queued analytics events locally instead of routing them onward. + * Telemetry sinks are disabled in this build. The exported functions remain so + * startup code does not need to special-case the open build. */ -import { attachAnalyticsSink } from './index.js' - -type LogEventMetadata = { [key: string]: boolean | number | undefined } - -function logEventImpl( - _eventName: string, - _metadata: LogEventMetadata, -): void { +export function initializeAnalyticsSink(): void { return } - -function logEventAsyncImpl( - _eventName: string, - _metadata: LogEventMetadata, -): Promise { - return Promise.resolve() -} - -export function initializeAnalyticsSink(): void { - attachAnalyticsSink({ - logEvent: logEventImpl, - logEventAsync: logEventAsyncImpl, - }) -} diff --git a/src/setup.ts b/src/setup.ts index 985e857..6c6eab7 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -368,7 +368,7 @@ export async function setup( ) // Start team memory sync watcher } } - initSinks() // Attach error log + analytics sinks and drain queued events + initSinks() // Attach error log sink and analytics compatibility stubs // Session-success-rate denominator. Emit immediately after the analytics // sink is attached — before any parsing, fetching, or I/O that could throw. diff --git a/src/utils/sinks.ts b/src/utils/sinks.ts index 386ce6a..ca5b4ec 100644 --- a/src/utils/sinks.ts +++ b/src/utils/sinks.ts @@ -2,10 +2,9 @@ import { initializeAnalyticsSink } from '../services/analytics/sink.js' import { initializeErrorLogSink } from './errorLogSink.js' /** - * Attach error log and analytics sinks, draining any events queued before - * attachment. Both inits are idempotent. Called from setup() for the default - * command; other entrypoints (subcommands, daemon, bridge) call this directly - * since they bypass setup(). + * Attach error log and analytics compatibility sinks. Both inits are + * idempotent. Called from setup() for the default command; other entrypoints + * (subcommands, daemon, bridge) call this directly since they bypass setup(). * * Leaf module — kept out of setup.ts to avoid the setup → commands → bridge * → setup import cycle.