From 913200d2a07b89e4cca49d3ba6f63f67a160bade Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:53:24 -0700 Subject: [PATCH] fix(telemetry): tighten codex trust-grant classification --- src/main/codex-accounts/wsl-codex-command.ts | 5 +++-- src/main/codex/codex-hook-trust-grant.ts | 13 +++---------- src/main/codex/codex-trust-grant-telemetry.test.ts | 14 +++++++++++++- src/main/codex/codex-trust-grant-telemetry.ts | 12 +++++++----- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/main/codex-accounts/wsl-codex-command.ts b/src/main/codex-accounts/wsl-codex-command.ts index 8b78fdf5570..92ead25fb76 100644 --- a/src/main/codex-accounts/wsl-codex-command.ts +++ b/src/main/codex-accounts/wsl-codex-command.ts @@ -6,6 +6,7 @@ import { } from '../../shared/wsl-login-shell-command' export const WSL_CODEX_AVAILABILITY_TIMEOUT_MS = 5_000 +export const WSL_CODEX_NOT_FOUND_MESSAGE = 'Codex CLI not found in the WSL login-shell PATH.' export function buildWslCodexAvailabilityArgs(distro: string): string[] { const command = [buildCodexPathLookup(), '[ -n "$resolved" ]'].join('\n') @@ -16,7 +17,7 @@ export function buildWslCodexIdentityArgs(distro: string): string[] { const command = [ buildCodexPathLookup(), 'if [ -z "$resolved" ]; then', - " printf '%s\\n' 'Codex CLI not found in the WSL login-shell PATH.' >&2", + ` printf '%s\\n' '${WSL_CODEX_NOT_FOUND_MESSAGE}' >&2`, ' exit 127', 'fi', 'printf \'%s\\n\' "$resolved"', @@ -29,7 +30,7 @@ export function buildWslCodexAppServerArgs(distro: string, linuxHomePath: string const command = [ buildCodexPathLookup(), 'if [ -z "$resolved" ]; then', - " printf '%s\\n' 'Codex CLI not found in the WSL login-shell PATH.' >&2", + ` printf '%s\\n' '${WSL_CODEX_NOT_FOUND_MESSAGE}' >&2`, ' exit 127', 'fi', `export CODEX_HOME=${quotePosixShell(linuxHomePath)}`, diff --git a/src/main/codex/codex-hook-trust-grant.ts b/src/main/codex/codex-hook-trust-grant.ts index a5cc1440226..94eb3117b45 100644 --- a/src/main/codex/codex-hook-trust-grant.ts +++ b/src/main/codex/codex-hook-trust-grant.ts @@ -64,21 +64,14 @@ export type CodexManagedTrustGrantOutcome = | { lane: 'rpc'; entries: CodexTrustEntry[] } | { lane: 'fallback'; reason: CodexTrustGrantFallbackReason } -export type CodexTrustGrantDiagnostics = { - granted: number - ledgerHits: number - fellBack: number - verifyFailed: number - lastFallbackReason: CodexTrustGrantFallbackReason | null -} - -const diagnostics: CodexTrustGrantDiagnostics = { +const diagnostics = { granted: 0, ledgerHits: 0, fellBack: 0, verifyFailed: 0, - lastFallbackReason: null + lastFallbackReason: null as CodexTrustGrantFallbackReason | null } +export type CodexTrustGrantDiagnostics = typeof diagnostics const transientRetryAfterByHost = new Map() export function getCodexTrustGrantDiagnostics(): CodexTrustGrantDiagnostics { diff --git a/src/main/codex/codex-trust-grant-telemetry.test.ts b/src/main/codex/codex-trust-grant-telemetry.test.ts index d49bfd78a34..ead60b60df0 100644 --- a/src/main/codex/codex-trust-grant-telemetry.test.ts +++ b/src/main/codex/codex-trust-grant-telemetry.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest' +import { WSL_CODEX_NOT_FOUND_MESSAGE } from '../codex-accounts/wsl-codex-command' import { CodexAppServerTimeoutError } from './codex-app-server-client' import { classifyCodexTrustGrantError } from './codex-trust-grant-telemetry' @@ -6,7 +7,18 @@ describe('classifyCodexTrustGrantError', () => { it.each([ [new CodexAppServerTimeoutError('entry exceeded 20000ms session deadline'), 'timeout'], [new Error('spawn codex ENOENT'), 'binary-missing'], - [new Error('spawn wsl.exe ENOENT'), 'binary-missing'], + [new Error('spawn /Users/ada/.local/bin/codex ENOENT'), 'binary-missing'], + [ + new Error( + `codex app-server exited before completing the session: ${WSL_CODEX_NOT_FOUND_MESSAGE}` + ), + 'binary-missing' + ], + [new Error('spawn wsl.exe ENOENT'), 'unexpected'], + [ + new Error("ENOENT: no such file or directory, open '/home/ada/.codex/config.toml'"), + 'unexpected' + ], [new Error('codex trust-grant entry bundle not found'), 'entry-failed'], [new Error('codex trust-grant entry produced no result (exit 1)'), 'entry-failed'], [ diff --git a/src/main/codex/codex-trust-grant-telemetry.ts b/src/main/codex/codex-trust-grant-telemetry.ts index 4875bb5beb2..25265219ba8 100644 --- a/src/main/codex/codex-trust-grant-telemetry.ts +++ b/src/main/codex/codex-trust-grant-telemetry.ts @@ -1,4 +1,5 @@ import type { CodexTrustGrantSessionVerifyClass } from './codex-app-server-client' +import { WSL_CODEX_NOT_FOUND_MESSAGE } from '../codex-accounts/wsl-codex-command' /** Which install surface asked for the grant: the system-default real ~/.codex * or a managed (mirror/per-account) home. Telemetry attribution only — the @@ -44,17 +45,18 @@ export function classifyCodexTrustGrantError(error: unknown): CodexTrustGrantErr if (message.includes('codex trust-grant entry')) { return 'entry-failed' } - // Why: ordered before the bare-ENOENT check — an early-exit message carries a - // stderr tail that could itself mention ENOENT after the binary already ran. + if ( + /^spawn (?:.*[\\/])?codex(?:\.(?:cmd|exe|bat))? ENOENT$/.test(message) || + message.includes(WSL_CODEX_NOT_FOUND_MESSAGE) + ) { + return 'binary-missing' + } if (message.includes('exited before completing the session')) { return 'early-exit' } if (/codex app-server \S+ failed:/.test(message)) { return 'rpc-failed' } - if (message.includes('ENOENT')) { - return 'binary-missing' - } return 'unexpected' }