feat(cli/telemetry): surface unrecognized agents in the agent_runtime=null bucket#1978
Conversation
…=null bucket agent_runtime is a closed allowlist: an agent we have no rule for collapses to null with no trace of what it was, so ~18% of CLI users are unattributable and new agents stay invisible until reverse-engineered by hand. Add detectAgentHints(), a self-populating residual signal computed only for the null bucket (gated off classified events): - agent_hint: value of AGENT / AI_AGENT (the emerging self-identification convention; Crush and Goose set AGENT=<name>) — names agents the allowlist misses. - term_program: raw TERM_PROGRAM (editor name) — catches the IDE-terminal class the same way the cursor/windsurf rules do. - agent_env_hints: sorted, comma-joined "agent-ish" env-var KEY names present but matched by no vendor rule — a fingerprint that clusters by agent. Privacy stays consistent with the existing "never read secret-shaped values" stance: agent_env_hints emits key names only; the three value-reads are vars whose sole purpose is non-secret identification, each passed through a strict short-slug allowlist so anything long/spaced/secret-shaped is dropped. Breaking down agent_hint / agent_env_hints filtered to agent_runtime IS NULL AND is_tty=false gives a ranked leaderboard of new agents to promote into VENDOR_RULES. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
99597ce to
b9bccf8
Compare
miguel-heygen
left a comment
There was a problem hiding this comment.
Reviewed b9bccf87; no prior reviews/comments were present. CI is green.
Strengths:
packages/cli/src/telemetry/system.ts:77correctly gates discovery hint collection toagent_runtime === null, so known-agent events do not get extra telemetry.packages/cli/src/telemetry/agent_runtime.ts:323keepsagent_env_hintsto env-var key names only, sorted/capped, which matches the intended low-content discovery signal.
Blocker:
packages/cli/src/telemetry/agent_runtime.ts:293/packages/cli/src/telemetry/agent_runtime.ts:320/packages/cli/src/telemetry/client.ts:95:sanitizeHint()only lowercases and applies^[a-z0-9_.-]{1,32}$, thentrackEvent()sends the result asagent_hint/term_program. That means short credential-shaped values still pass the “safe slug” filter and get sent to PostHog. I verified the exact regex accepts examples likeAGENT=sk-ant-api03,AGENT=AKIAIOSFODNN7EXAMPLE, andAGENT=github_pat_abcafter lowercasing. The PR privacy contract says secret-shaped values are dropped, but the test atpackages/cli/src/telemetry/agent_runtime.test.ts:358only proves an overlong secret-looking value is dropped. Please add an explicit secret-shape/credential-prefix/entropy guard, or otherwise avoid value capture for unknown values, plus tests for short credential-shaped strings.
Verdict: REQUEST CHANGES
Reasoning: The null-bucket discovery shape is sound, but the current sanitizer can still emit short credential-shaped env values as telemetry, which violates the privacy boundary this PR relies on.
— Magi
…ntial-shaped values Review feedback (Magi, #1978): the short-slug allowlist in sanitizeHint() still accepted short credential-shaped values (AGENT=sk-ant-api03, AGENT=AKIAIOSFODNN7EXAMPLE, AGENT=github_pat_abc), so the "never emit a secret" claim wasn't actually enforced — only overlong values were dropped. Add a credential-shape guard on top of the slug allowlist: - known token/credential prefixes (sk-, ghp_, github_pat_, akia, ya29, ...) - any unbroken alphanumeric run >= 16 chars (key bodies, hex, base64-ish), while agent names segment on _/-/. and keep each run short. Replace the single overlong-value test with the short credential shapes from the review (parametrized) plus a positive case (gemini_managed_agent) proving real multi-segment names still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
miguel-heygen
left a comment
There was a problem hiding this comment.
Re-reviewed 99a2ccc58 against the privacy blocker from my prior review.
The blocker is resolved:
packages/cli/src/telemetry/agent_runtime.ts:302adds explicit credential/token prefixes, including the short examples I flagged.packages/cli/src/telemetry/agent_runtime.ts:323adds the long unbroken alphanumeric-run guard, so short prefixless token bodies are also dropped.packages/cli/src/telemetry/agent_runtime.ts:330now requires both the safe slug shape and the credential guard before returning a hint.packages/cli/src/telemetry/agent_runtime.test.ts:366pins the exact short secret-shaped cases plus the positive multi-segment agent-name case.
I also sanity-checked the guard locally against the reviewed examples: sk-ant-api03, AKIAIOSFODNN7EXAMPLE, github_pat_abc, ghp_0123456789abcdef, ya29.a0veryrealtoken, and deadbeefdeadbeef01 all drop to null, while gemini_managed_agent, zed, and crush still pass.
Note: at the time of this review, the core CLI/code checks were green (Test, Typecheck, Lint, Build, CLI smoke, CodeQL, Windows CLI shim), while some long render/regression jobs were still pending in GitHub. No failing checks were present.
Verdict: APPROVE
Reasoning: The privacy boundary I requested is now enforced in code and covered by focused regression tests; remaining merge gating is CI completion, not code review.
— Magi
Why
agent_runtime(shipped v0.6.37) is a closed allowlist — an agent we have noVENDOR_RULEfor collapses toagent_runtime=nullwith no trace of what it was. Today that null bucket is ~87k users / ~18% of CLI users over the last 30 days, and it's on current0.7.xversions (not stale clients). ~50k of those are non-tty / non-CI / non-container — i.e. something is driving the CLI programmatically and we can't name it. New agents stay invisible until someone reverse-engineers their marker by hand.This adds a self-populating discovery signal so an unrecognized agent surfaces on its own instead of vanishing into
null.What
New
detectAgentHints()inagent_runtime.ts, computed only for the null bucket (gated off the ~82% of classified events insystem.ts):agent_hint— value ofAGENT/AI_AGENT, the emerging self-identification convention (Crush and Goose already setAGENT=<name>). Directly names agents the allowlist misses.term_program— rawTERM_PROGRAM(editor name). Catches the IDE-terminal class (Zed, new VS Code forks) the same way the existing cursor/windsurf rules do.agent_env_hints— sorted, comma-joined list of "agent-ish" env-var key names present but matched by no vendor rule (AGENT|ASSISTANT|COPILOT|CODEX|CLAUDE|CODING|LLM|_THREAD_ID|_SESSION_ID, minus SSH/GPG false-friends). A compact fingerprint that clusters by agent.Wired through
getSystemMeta()→trackEvent()like the existingagent_runtimesuper-property.Privacy
Consistent with the file's existing "never read secret-shaped values" stance:
agent_env_hintsemits key names only, never values.AGENT/AI_AGENT/TERM_PROGRAM) are vars whose sole purpose is non-secret identification, each passed through a strict^[a-z0-9_.-]{1,32}$slug filter — anything long, spaced, or secret-shaped is dropped tonull. Test asserts ansk-ant-…-shaped value returns null.No PII, paths, or content — the existing telemetry notice still holds.
Using it
Once released and users upgrade, breakdown
agent_hint/agent_env_hintsfiltered toagent_runtime IS NULL AND is_tty = false→ a ranked leaderboard of new agents. When one climbs, promote it to aVENDOR_RULEand it moves into the named buckets on the existing DAU-by-agent_runtime insight.Test plan
bunx vitest run packages/cli/src/telemetry/agent_runtime.test.ts— 6 new tests (AGENT/AI_AGENT capture, secret-shaped drop, TERM_PROGRAM, unknown key surfaced, SSH false-friend excluded)🤖 Generated with Claude Code