fix(fingerprint): update Claude Code fingerprint to v2.1.92 / sdk 0.80.0#2540
fix(fingerprint): update Claude Code fingerprint to v2.1.92 / sdk 0.80.0#25400oAstro wants to merge 3 commits intorouter-for-me:devfrom
Conversation
Captured via mitmproxy interception of live claude-code requests vs cliproxy-forwarded requests. Diffs observed: - UA: claude-cli/2.1.63 (external, cli) -> claude-cli/2.1.92 (external, sdk-cli) (entrypoint suffix changed from 'cli' to 'sdk-cli' in newer builds) - @anthropic-ai/sdk: 0.74.0 -> 0.80.0 - X-Stainless-Runtime-Version: v24.3.0 -> v24.14.0 - DefaultClaudeVersion fallback: 2.1.63 -> 2.1.92 - checkSystemInstructions hardcoded version: 2.1.63 -> 2.1.92 Additional missing headers added (observed in 2.1.92 direct traffic): - Accept-Language: * (now sent by the Stainless SDK) - Sec-Fetch-Mode: cors (browser-compat header added in newer builds) Missing beta flags added to base set: - advanced-tool-use-2025-11-20 - effort-2025-11-24 Both headers were absent from cliproxy-forwarded requests, creating a detectable fingerprint delta vs first-party claude-code sessions. Source: mitmproxy capture comparing cliproxy-forwarded vs direct claude-code 2.1.92 requests to api.anthropic.com.
There was a problem hiding this comment.
Code Review
This pull request updates the Claude executor and device profile to align with Claude Code version 2.1.92 and SDK 0.80.0. Key changes include adding new beta features to the headers, introducing Accept-Language and Sec-Fetch-Mode headers, and updating version-related constants and function calls. The review feedback recommends centralizing the hardcoded version string into a single constant within the helps package to prevent duplication and ensure consistency across multiple files.
| defaultClaudeFingerprintUserAgent = "claude-cli/2.1.92 (external, sdk-cli)" | ||
| defaultClaudeFingerprintPackageVersion = "0.80.0" | ||
| defaultClaudeFingerprintRuntimeVersion = "v24.14.0" |
There was a problem hiding this comment.
The version string 2.1.92 is now used in multiple locations across the codebase. It is better to define it as a single constant to ensure consistency and simplify future updates.
| defaultClaudeFingerprintUserAgent = "claude-cli/2.1.92 (external, sdk-cli)" | |
| defaultClaudeFingerprintPackageVersion = "0.80.0" | |
| defaultClaudeFingerprintRuntimeVersion = "v24.14.0" | |
| DefaultClaudeCLIVersion = "2.1.92" | |
| defaultClaudeFingerprintUserAgent = "claude-cli/" + DefaultClaudeCLIVersion + " (external, sdk-cli)" | |
| defaultClaudeFingerprintPackageVersion = "0.80.0" | |
| defaultClaudeFingerprintRuntimeVersion = "v24.14.0" |
| return strconv.Itoa(version.major) + "." + strconv.Itoa(version.minor) + "." + strconv.Itoa(version.patch) | ||
| } | ||
| return "2.1.63" | ||
| return "2.1.92" |
|
|
||
| func checkSystemInstructions(payload []byte) []byte { | ||
| return checkSystemInstructionsWithSigningMode(payload, false, false, "2.1.63", "", "") | ||
| return checkSystemInstructionsWithSigningMode(payload, false, false, "2.1.92", "", "") |
There was a problem hiding this comment.
Use the exported DefaultClaudeCLIVersion constant from the helps package to avoid duplication and potential drift during future version bumps.
| return checkSystemInstructionsWithSigningMode(payload, false, false, "2.1.92", "", "") | |
| return checkSystemInstructionsWithSigningMode(payload, false, false, helps.DefaultClaudeCLIVersion, "", "") |
|
|
||
| func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte { | ||
| return checkSystemInstructionsWithSigningMode(payload, strictMode, false, "2.1.63", "", "") | ||
| return checkSystemInstructionsWithSigningMode(payload, strictMode, false, "2.1.92", "", "") |
There was a problem hiding this comment.
Use the exported DefaultClaudeCLIVersion constant from the helps package to maintain consistency with other parts of the executor.
| return checkSystemInstructionsWithSigningMode(payload, strictMode, false, "2.1.92", "", "") | |
| return checkSystemInstructionsWithSigningMode(payload, strictMode, false, helps.DefaultClaudeCLIVersion, "", "") |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52e3eeecd0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| defaultClaudeFingerprintUserAgent = "claude-cli/2.1.63 (external, cli)" | ||
| defaultClaudeFingerprintPackageVersion = "0.74.0" | ||
| defaultClaudeFingerprintRuntimeVersion = "v24.3.0" | ||
| defaultClaudeFingerprintUserAgent = "claude-cli/2.1.92 (external, sdk-cli)" |
There was a problem hiding this comment.
Keep default billing entrypoint in sync with sdk-cli UA
Changing the default fingerprint UA to claude-cli/2.1.92 (external, sdk-cli) introduces an internal mismatch because billing-header generation still falls back to cc_entrypoint=cli when no Claude UA is present (see checkSystemInstructions*/generateBillingHeader in claude_executor.go). In auto-cloak flows for non-Claude clients and in CountTokens, this means the same outbound request now advertises sdk-cli in User-Agent but cli in x-anthropic-billing-header, which is a detectable fingerprint gap and undermines the purpose of this update.
Useful? React with 👍 / 👎.
…g order
Two remaining fingerprint deltas vs claude-code 2.1.92 captured via
mitmproxy after the initial version bump:
metadata.user_id format (2.1.92 changed encoding):
Old: user_[64hex]_account_[UUID]_session_[UUID] (flat string)
New: {"device_id":"[64hex]","account_uuid":"[UUID]","session_id":"[UUID]"} (JSON string)
generateFakeUserIDWithSession() added so session_id in metadata
stays consistent with X-Claude-Code-Session-Id header (both derived
from CachedSessionID per apiKey).
accept-encoding order (Stainless SDK 0.80.0 changed ordering, dropped zstd):
Old: gzip, deflate, br, zstd
New: br, gzip, deflate
luispater
left a comment
There was a problem hiding this comment.
Summary
Thanks for updating the Claude fingerprint baseline and for aligning the billing entrypoint with the resolved outbound UA. I found one blocking regression in the default cloaking path.
Blocking
internal/runtime/executor/claude_executor.go:1249still callshelps.GenerateFakeUserID()whencache-user-idis false or unset.internal/runtime/executor/helps/cloak_utils.go:20generatesmetadata.user_id.session_idfrom a fresh UUID, whileinternal/runtime/executor/claude_executor.go:893always sendsX-Claude-Code-Session-Idfromhelps.CachedSessionID(apiKey).- Because the non-cached path is still active by default,
metadata.user_id.session_idandX-Claude-Code-Session-Iddiverge in the common configuration, so the fingerprint delta this PR is meant to remove is still present.
Non-blocking
- The new tests cover the cache-enabled path and the
CountTokensentrypoint fix, but they do not assert the default non-cachedmetadata.user_id.session_id == X-Claude-Code-Session-Idbehavior.
Test plan
go test -run 'TestResolveOutboundClaudeEntrypoint_|TestClaudeExecutor_CountTokens_BillingEntrypointMatchesResolvedUserAgent|TestClaudeExecutor_GeneratesNewUserIDByDefault|TestClaudeExecutor_ReusesUserIDAcrossModelsWhenCacheEnabled|TestClaudeExecutor_Execute_SetsCompressedAcceptEncoding' ./internal/runtime/executorgo build -o test-output ./cmd/server- Added a temporary regression test in a clean PR checkout to assert
metadata.user_id.session_id == X-Claude-Code-Session-Idfor the default config; it failed on this branch with different UUIDs.
What
Updates the default Claude Code fingerprint from the stale v2.1.63 baseline to the current v2.1.92. All captures are live mitmproxy intercepts (socks5 mode) of actual requests to
api.anthropic.com.Changes
helps/claude_device_profile.goclaude-cli/2.1.63 (external, cli)claude-cli/2.1.92 (external, sdk-cli)0.74.00.80.0v24.3.0v24.14.02.1.632.1.92Note: entrypoint suffix in UA changed from
clitosdk-cliin 2.1.92.parseEntrypointFromUAalready handles this correctly —cc_entrypointin billing header is derived from the incoming client UA, not the default constant.claude_executor.go"2.1.63"strings incheckSystemInstructions/checkSystemInstructionsWithMode→"2.1.92"advanced-tool-use-2025-11-20,effort-2025-11-24Accept-Language: *,Sec-Fetch-Mode: cors(now sent by Stainless SDK 0.80.0)accept-encodingorder:gzip, deflate, br, zstd→br, gzip, deflate(SDK 0.80.0 dropped zstd, reordered)helps/cloak_utils.go+helps/user_id_cache.gometadata.user_idformat changed in 2.1.92 from flat string to JSON string:user_[64hex]_account_[UUID]_session_[UUID]{"device_id":"[64hex]","account_uuid":"[UUID]","session_id":"[UUID]"}Added
generateFakeUserIDWithSession()sometadata.user_id.session_idstays consistent withX-Claude-Code-Session-Id(both useCachedSessionID(apiKey)).Live mitmproxy captures (3-way comparison)
Captured via mitmproxy in SOCKS5 mode intercepting
api.anthropic.com. Auth redacted.OLD cliproxy (eceasy/cli-proxy-api, pre-fix)
NEW cliproxy (this branch, compiled from source)
Direct claude-code 2.1.92 (reference / ground truth)
Verification
go build ./...cleango test ./...— only pre-existingTestEnsureQwenSystemMessage_MergeStringSystemfailure (already failing ondevbranch tip before this change, unrelated to fingerprint code)