Skip to content

Fix: Recognise Claude Code's second product token - #1369

Merged
huang195 merged 1 commit into
mainfrom
fix/claude-code-ua
Oct 13, 2026
Merged

huang195 merged 1 commit into
mainfrom
fix/claude-code-ua

Conversation

@mrsabath

@mrsabath mrsabath commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Closes #1363.

The bug

claude-code was a value in knownClients but never a key. ParseUserAgent looks the first product token up against the map's keys, so Claude Code's Agent-SDK-shaped User-Agent missed and came back unrecognised:

User-Agent Events Name before Name after
claude-cli/2.1.233 (external, claude-vscode, agent-sdk/0.3.233) 469 claude-code ✅ claude-code
claude-code/2.1.233 (claude-vscode, agent-sdk/0.3.233) 70 "" ❌ claude-code ✅
Claude-User (claude-code/2.1.233; +https://support.anthropic.com/) 12 "" "" — deliberate

Why it matters

Those 70 events are ~13% of this capture's Claude Code traffic. Unrecognised, their Label() is the whole raw User-Agent, so they became a series key of their own — pooling into agentop's catch-all Other row with their spend, while IsKnownAgent was false for them wherever that gates (core/cost/usage). Claude Code is the reference integration, so this is the one agent whose attribution was assumed correct.

Session attribution was never affected. AffinityName returns claude-code for all three shapes, because its comment scan compares against canonical names rather than this map's keys. This is a labeling/aggregation fix, not a session-grouping one.

The third shape stays unrecognised

Claude-User (claude-code/…) is Claude Code's WebFetch, and leaving it unclaimed is deliberate — AffinityName's doc comment has the reasoning: naming a row out of a comment would move those rows to a different agent in the ledger and the AGENTS pane than the history beside them.

It survives the fix incidentally rather than by rule: its first token is Claude-User, and trailingKnownClient cuts on / without trimming punctuation, so the later token reads as (claude-code and misses. That is a property of the parser's shape rather than something it states, which is exactly why there is now a test pinning it.

Same class of bug as #1337

That PR fixed this for Codex — one agent sending more than one product token, with the lookup table covering only some of them. bob / bob-shell / opencode are worth the same look; OpenCode reportedly uses one UA for every mode, but that is from a source comment rather than a capture.

Verification

  • Reproduced the bug first, then fixed it: row 2 went from Name="" IsKnown=false to Name="claude-code" Version="2.1.233".
  • Confirmed the new test fails without the map entry (temporarily removed it) and that the Claude-User negative passes either way — so it is not accidentally coupled to the fix.
  • core/pipeline suite green; core/cost/..., core/session/..., core/sessionapi green. go vet and gofmt clean.
  • cmd/agentop/tui has 3 pre-existing failures on this base commit (router / server-picker, unrelated to User-Agent parsing); verified identical on an untouched checkout of the same commit.

Unknown

Whether headless Claude Code (claude -p) is one of these two shapes or a fourth. Both recognised shapes name claude-vscode and agent-sdk, so they look like different components (CLI binary vs Agent SDK) rather than interactive-vs-headless, but I have no headless capture to confirm it.

Assisted-By: Claude (Anthropic AI) noreply@anthropic.com

Summary by CodeRabbit

  • New Features
    • Claude Code User-Agents using the claude-code token are now recognized, alongside the existing claude-cli form.
  • Bug Fixes
    • Claude Code User-Agents now resolve with the expected version and product label. Claude-User User-Agents remain unrecognized.

Closes #1363.

claude-code was a VALUE in knownClients but never a KEY. ParseUserAgent
looks the first product token up against the map's keys, so the
Agent-SDK-shaped User-Agent — "claude-code/2.1.233 (claude-vscode,
agent-sdk/0.3.233)" — missed and came back unrecognised.

Measured on one laptop proxy's session store, that is 70 of 551 Claude
Code events, ~13%: they pooled into agentop's catch-all Other row with
their spend, and IsKnownAgent was false for them wherever that gates
(core/cost/usage). Claude Code is the reference integration, so this is
the one agent whose attribution was assumed correct.

Session attribution was never affected — AffinityName returns
claude-code for every shape, because its comment scan compares against
canonical names rather than this map's keys. This is a labeling and
aggregation fix, not a session-grouping one.

The same class of bug as #1337 fixed for Codex: one agent sending more
than one product token, with the lookup table covering only some of
them.

Tests pin all three live User-Agents, including the negative:
"Claude-User (claude-code/…)" must STAY unrecognised, since naming a row
out of a comment would move those rows to a different agent in the
ledger and the AGENTS pane than the history beside them. Verified that
the new test fails without the map entry and that the Claude-User case
passes either way, so it is not coupled to the fix.

Assisted-By: Claude (Anthropic AI) <noreply@anthropic.com>
Signed-off-by: Mariusz Sabath <mrsabath@gmail.com>
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4663cdb8-be1c-404d-83ad-fb07b5cf14d1
📥 Commits

Reviewing files that changed from the base of the PR and between aa391a1 and c8881de.

📒 Files selected for processing (2)
  • core/pipeline/client.go
  • core/pipeline/client_test.go

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

The client lookup now recognizes claude-code as a product token. Tests cover both recognized Claude Code User-Agent forms and confirm that Claude-User remains unrecognized.

Changes: Claude Code recognition

Layer / File(s) Summary
Client lookup and validation
core/pipeline/client.go, core/pipeline/client_test.go
The client map recognizes the claude-code token. Tests check the parsed name, version, label, and known-agent result for two User-Agent forms. A separate test confirms Claude-User remains unrecognized while AffinityName() returns claude-code.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: alan-cha

Merge Risk: ⚪ Minimal · up to c8881

The reported Claude Code User-Agent forms are covered, with no actionable merge risk identified. The headless User-Agent shape remains unknown but is outside the requested scope.

Pre-merge checks · Passed 5
Passed Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: recognizing Claude Code's second product token.
Linked Issues check Passed The change satisfies #1363. core/pipeline/client.go adds "claude-code": "claude-code" to knownClients, so the second User-Agent shape resolves to the canonical name. `core/pipeline/client_test.g…
Out of Scope Changes check Passed The pull request changes only the User-Agent recognition map and focused pipeline tests. The comments explain the #1363 behavior and the intentional negative case. The changes do not implement the exp…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 2 files.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@huang195 huang195 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Adding claude-code as a key also feeds trailingKnownClient, and I checked that doesn't widen anything: Claude-User (claude-code/…; splits to (claude-code and misses, which the new negative test pins. AffinityName is unaffected since it compares against canonical names.

Two comment nits, outside the diff so not inline, neither blocking:

  1. core/pipeline/client.go:597-598 — AffinityName's doc says "the key is the product token claude-cli". With claude-code now also a key, that is half true; maybe "…while claude-cli's key is the product token, not the name".
  2. core/pipeline/client.go:259-261 — the knownClients godoc still presents claude-cli as Claude Code's only product token; could mention it sends both.

@huang195
huang195 merged commit c841538 into main Oct 13, 2026
30 checks passed
@huang195
huang195 deleted the fix/claude-code-ua branch October 13, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Bug: Claude Code's second User-Agent is unrecognised, so ~13% of its events label as Other

3 participants