Skip to content

Attribute CLI telemetry to Claude Code and Codex command trees - #480

Merged
andrelandgraf merged 4 commits into
mainfrom
feat/cli-agent-attribution
Aug 25, 2026
Merged

Attribute CLI telemetry to Claude Code and Codex command trees#480
andrelandgraf merged 4 commits into
mainfrom
feat/cli-agent-attribution

Conversation

@andrelandgraf

@andrelandgraf andrelandgraf commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Problem

Telemetry cannot tell a neon run started by a coding agent from one a person typed. Agents drive the CLI through a shell they own, so the process the agent launches is often a wrapper and the CLI turns up several levels down the tree. Checking the immediate caller answers the wrong question.

What decides attribution

getCliAgent(env) in packages/cli/src/env.ts reads the environment a launched command tree inherits, so it identifies a neon process anywhere under a Claude Code or Codex launch rather than only a direct child.

Session Required
Claude Code CLAUDE_CODE_CHILD_SESSION=1
Codex CODEX_CI=1 and a non-empty CODEX_THREAD_ID or CODEX_SESSION_ID

Codex ids on their own mark a command the user typed in the Codex shell, so those runs stay unattributed. CLAUDECODE, CLAUDE_CODE, CLAUDE_CLI, CODEX, CODEX_SANDBOX and CODEX_SANDBOX_NETWORK_DISABLED are ambient or sandbox utility markers and never attribute on their own. When a Claude session and a Codex session both look active, the markers are nested and the property is omitted.

The property

agent rides on CLI Started, cli_command_success and CLI Error.

Claude Code:

{ "command": "branches list", "ci": false, "agent": "claude-code" }

Codex:

{ "command": "branches list", "ci": false, "agent": "codex" }

Unattributed:

{ "command": "branches list", "ci": false }

Three outcomes: claude-code, codex, or the property absent. Absent covers the human case and the conflicting-marker case.

How the CLI is invoked and what it prints are unchanged, and neon init behaves the same. No changeset: nothing a package consumer can call or see moved.

Also in here

src/init/agent_snapshot.test.ts and src/init/auth.test.ts spawn the real built CLI. Both set NEON_NO_ANALYTICS=1, which is read nowhere in the repo, so those runs were live. Both now pass --no-analytics, the flag the CLI honors.

src/analytics.test.ts clears CODEX_CI, CODEX_THREAD_ID and CODEX_SESSION_ID before asserting the Claude Code case, and unstubs env after each test. Run the suite inside a Codex session and the inherited markers would otherwise make it a conflict and omit the property.

Verification

  • pnpm --filter neon test - 4470 passed, 6 skipped; 168 files passed, 3 skipped
  • pnpm lint:ci - clean
  • CODEX_CI=1 CODEX_THREAD_ID=review-repro pnpm --filter neon exec vitest run src/analytics.test.ts - 17 passed, which is the isolation the plain run cannot catch

Cases covered: Claude child session; Codex thread id; Codex session id; Codex ids with no CODEX_CI; CLAUDE_CODE_CHILD_SESSION=false with whitespace-only ids; the ambient and sandbox marker set; a Claude-inside-Codex conflict; agent present on the error event context.

Not verified: no event was sent to a live endpoint. Assertions sit at the property builders rather than at the wire.

For your attention

  • Downstream ingestion does not read agent yet. Events carry it from the next release and it stays invisible in reporting until ingestion is updated.
  • The contract is environment markers, so it detects what each tool sets today. A release of either tool that renames or drops a marker returns those runs to the unattributed case with no failure.

@andrelandgraf andrelandgraf changed the title Attribute CLI telemetry to Claude Code and Codex Attribute CLI telemetry to Claude Code and Codex command trees Aug 25, 2026
@andrelandgraf
andrelandgraf merged commit 9224b2b into main Aug 25, 2026
18 checks passed
@andrelandgraf
andrelandgraf deleted the feat/cli-agent-attribution branch August 25, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant