From 92e95ed8062e8cb6757027c4bdd8485f1915ab92 Mon Sep 17 00:00:00 2001 From: Riddhi Dutta Date: Wed, 3 Dec 2025 10:56:12 +0530 Subject: [PATCH] track github repository names in telemetry events (#13670) Co-authored-by: riddhi --- .../clearcut-logger/clearcut-logger.test.ts | 78 +++++++++++++++++++ .../clearcut-logger/clearcut-logger.ts | 23 ++++++ .../clearcut-logger/event-metadata-key.ts | 5 +- 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts index 3b8747b629..5c5f3d57a4 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.test.ts @@ -427,6 +427,84 @@ describe('ClearcutLogger', () => { }); }); + describe('GITHUB_REPOSITORY metadata', () => { + it('includes hashed repository when GITHUB_REPOSITORY is set', () => { + vi.stubEnv('GITHUB_REPOSITORY', 'google/gemini-cli'); + const { logger } = setup({}); + + const event = logger?.createLogEvent(EventNames.API_ERROR, []); + const repositoryMetadata = event?.event_metadata[0].find( + (item) => + item.gemini_cli_key === + EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH, + ); + expect(repositoryMetadata).toBeDefined(); + expect(repositoryMetadata?.value).toMatch(/^[a-f0-9]{64}$/); + expect(repositoryMetadata?.value).not.toBe('google/gemini-cli'); + }); + + it('hashes repository name consistently', () => { + vi.stubEnv('GITHUB_REPOSITORY', 'google/gemini-cli'); + const { logger } = setup({}); + + const event1 = logger?.createLogEvent(EventNames.API_ERROR, []); + const event2 = logger?.createLogEvent(EventNames.API_ERROR, []); + + const hash1 = event1?.event_metadata[0].find( + (item) => + item.gemini_cli_key === + EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH, + )?.value; + const hash2 = event2?.event_metadata[0].find( + (item) => + item.gemini_cli_key === + EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH, + )?.value; + + expect(hash1).toBeDefined(); + expect(hash2).toBeDefined(); + expect(hash1).toBe(hash2); + }); + + it('produces different hashes for different repositories', () => { + vi.stubEnv('GITHUB_REPOSITORY', 'google/gemini-cli'); + const { logger: logger1 } = setup({}); + const event1 = logger1?.createLogEvent(EventNames.API_ERROR, []); + const hash1 = event1?.event_metadata[0].find( + (item) => + item.gemini_cli_key === + EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH, + )?.value; + + vi.stubEnv('GITHUB_REPOSITORY', 'google/other-repo'); + ClearcutLogger.clearInstance(); + const { logger: logger2 } = setup({}); + const event2 = logger2?.createLogEvent(EventNames.API_ERROR, []); + const hash2 = event2?.event_metadata[0].find( + (item) => + item.gemini_cli_key === + EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH, + )?.value; + + expect(hash1).toBeDefined(); + expect(hash2).toBeDefined(); + expect(hash1).not.toBe(hash2); + }); + + it('does not include repository when GITHUB_REPOSITORY is not set', () => { + vi.stubEnv('GITHUB_REPOSITORY', undefined); + const { logger } = setup({}); + + const event = logger?.createLogEvent(EventNames.API_ERROR, []); + const hasRepository = event?.event_metadata[0].some( + (item) => + item.gemini_cli_key === + EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH, + ); + expect(hasRepository).toBe(false); + }); + }); + describe('logChatCompressionEvent', () => { it('logs an event with proper fields', () => { const { logger } = setup(); diff --git a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts index d6468816d9..cd29a97640 100644 --- a/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts +++ b/packages/core/src/telemetry/clearcut-logger/clearcut-logger.ts @@ -4,6 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { createHash } from 'node:crypto'; import { HttpsProxyAgent } from 'https-proxy-agent'; import type { StartSessionEvent, @@ -155,6 +156,13 @@ function determineGHWorkflowName(): string | undefined { return process.env['GH_WORKFLOW_NAME']; } +/** + * Determines the GitHub repository name if the CLI is running in a GitHub Actions environment. + */ +function determineGHRepositoryName(): string | undefined { + return process.env['GITHUB_REPOSITORY']; +} + /** * Clearcut URL to send logging events to. */ @@ -186,6 +194,7 @@ export class ClearcutLogger { private promptId: string = ''; private readonly installationManager: InstallationManager; private readonly userAccountManager: UserAccountManager; + private readonly hashedGHRepositoryName?: string; /** * Queue of pending events that need to be flushed to the server. New events @@ -215,6 +224,13 @@ export class ClearcutLogger { this.promptId = config?.getSessionId() ?? ''; this.installationManager = new InstallationManager(); this.userAccountManager = new UserAccountManager(); + + const ghRepositoryName = determineGHRepositoryName(); + if (ghRepositoryName) { + this.hashedGHRepositoryName = createHash('sha256') + .update(ghRepositoryName) + .digest('hex'); + } } static getInstance(config?: Config): ClearcutLogger | undefined { @@ -323,6 +339,13 @@ export class ClearcutLogger { }); } + if (this.hashedGHRepositoryName) { + baseMetadata.push({ + gemini_cli_key: EventMetadataKey.GEMINI_CLI_GH_REPOSITORY_NAME_HASH, + value: this.hashedGHRepositoryName, + }); + } + const logEvent: LogEvent = { console_type: 'GEMINI_CLI', application: 102, // GEMINI_CLI diff --git a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts index 333bbaf6fa..dc7ad7d672 100644 --- a/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts +++ b/packages/core/src/telemetry/clearcut-logger/event-metadata-key.ts @@ -7,7 +7,7 @@ // Defines valid event metadata keys for Clearcut logging. export enum EventMetadataKey { // Deleted enums: 24 - // Next ID: 131 + // Next ID: 133 GEMINI_CLI_KEY_UNKNOWN = 0, @@ -197,6 +197,9 @@ export enum EventMetadataKey { // Logs the active experiment IDs for the session. GEMINI_CLI_EXPERIMENT_IDS = 131, + // Logs the repository name of the GitHub Action that triggered the session. + GEMINI_CLI_GH_REPOSITORY_NAME_HASH = 132, + // ========================================================================== // Loop Detected Event Keys // ===========================================================================