-
-
Notifications
You must be signed in to change notification settings - Fork 7.6k
feat(claude): upgrade Claude Code 2.1.258 wire fingerprint and model behavioral parity #5456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
17db7c4
bc3093e
80e9e9d
897a28c
7cfa992
0d3dafe
5fd4609
98e30a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -148,20 +148,44 @@ func computeFingerprint(messageText, version string) string { | |
|
|
||
| // generateBillingHeader creates the x-anthropic-billing-header text block that | ||
| // Claude Code prepends to its system prompt. cch is present only on signed paths. | ||
| func generateBillingHeader(cchSigning bool, version, messageText, entrypoint, workload string) string { | ||
| func generateBillingHeader(cchSigning bool, version, messageText, entrypoint, workload string, isSubagent bool, prevReq, promptID string) string { | ||
| if entrypoint == "" { | ||
| entrypoint = "cli" | ||
| } | ||
| buildHash := computeFingerprint(messageText, version) | ||
| workloadPart := "" | ||
| var b strings.Builder | ||
| b.WriteString("x-anthropic-billing-header: cc_version=") | ||
| b.WriteString(version) | ||
| b.WriteByte('.') | ||
| b.WriteString(buildHash) | ||
| b.WriteString("; cc_entrypoint=") | ||
| b.WriteString(entrypoint) | ||
| b.WriteByte(';') | ||
|
|
||
| if cchSigning { | ||
| b.WriteString(" cch=00000;") | ||
| } | ||
| if workload != "" { | ||
| workloadPart = fmt.Sprintf(" cc_workload=%s;", workload) | ||
| b.WriteString(" cc_workload=") | ||
| b.WriteString(workload) | ||
| b.WriteByte(';') | ||
| } | ||
| if isSubagent { | ||
| b.WriteString(" cc_is_subagent=true;") | ||
| } | ||
|
|
||
| if cchSigning { | ||
| return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=%s; cch=00000;%s", version, buildHash, entrypoint, workloadPart) | ||
| if prevReq != "" { | ||
| b.WriteString(" cc_prev_req=") | ||
| b.WriteString(prevReq) | ||
| b.WriteByte(';') | ||
| } | ||
| if promptID != "" { | ||
| b.WriteString(" cc_prompt_id=") | ||
| b.WriteString(promptID) | ||
| b.WriteByte(';') | ||
| } | ||
| } | ||
| return fmt.Sprintf("x-anthropic-billing-header: cc_version=%s.%s; cc_entrypoint=%s;%s", version, buildHash, entrypoint, workloadPart) | ||
| return b.String() | ||
| } | ||
|
|
||
| func claudeBillingFingerprintMessageText(payload []byte) string { | ||
|
|
@@ -191,17 +215,34 @@ func claudeBillingFingerprintMessageText(payload []byte) string { | |
| } | ||
|
|
||
| func claudeCCHFallbackBillingHeader(ctx context.Context, cfg *config.Config, payload []byte, entrypoint string) string { | ||
| prevReq, promptID := helps.ExtractClaudeBillingTags(payload) | ||
| continuityCtx := helps.ClaudeContinuityContextFromContext(ctx) | ||
| if prevReq == "" && continuityCtx != nil { | ||
| prevReq = continuityCtx.PreviousRequestID | ||
| } | ||
| if promptID == "" && continuityCtx != nil { | ||
| promptID = continuityCtx.PromptID | ||
| } | ||
| incomingHeaders := resolveIncomingClaudeHeaders(ctx, helps.IncomingHeadersFromContext(ctx)) | ||
| isSubagent := helps.IsClaudeSubagentRequest(incomingHeaders, payload) | ||
| return generateBillingHeader( | ||
| true, | ||
| helps.DefaultClaudeVersion(cfg), | ||
| claudeBillingFingerprintMessageText(payload), | ||
| entrypoint, | ||
| getWorkloadFromContext(ctx), | ||
| isSubagent, | ||
| prevReq, | ||
| promptID, | ||
| ) | ||
| } | ||
|
|
||
| const claudeCodeCLIIdentity = "You are Claude Code, Anthropic's official CLI for Claude." | ||
|
|
||
| const claudeCodeFableReportingOutcomes = `# Reporting outcomes | ||
|
|
||
| Report what actually happened, not what you intended. When you say something is done, sent, saved, fixed, or verified, that claim must rest on a result you observed in this session — tool output, the file as it now reads, the page as it now loads — not on what the step should have produced. If you did not check, say you did not check. If any step failed, was skipped, or came back different from what you expected, say so in the first sentence of your report, before anything else, even when the rest of the work succeeded. Never quietly work around a failure in a way that makes it look resolved; a problem the user can see is recoverable, one your summary hides is not. When you stop before the task is complete, your first line says so plainly and names what is left. Do not describe partial work as done, and do not let a summary read as more certain than the evidence behind it.` | ||
|
|
||
| func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte { | ||
| return checkSystemInstructionsWithSigningMode(payload, strictMode, false, "2.1.258", "cli", "") | ||
| } | ||
|
|
@@ -212,17 +253,38 @@ func checkSystemInstructionsWithMode(payload []byte, strictMode bool) []byte { | |
| // Claude models give it operator-level authority without changing the cached | ||
| // top-level prefix. | ||
| func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, cchSigning bool, version, entrypoint, workload string) []byte { | ||
| return checkSystemInstructionsWithSigningModeAt(payload, strictMode, cchSigning, version, entrypoint, workload, time.Now()) | ||
| return checkSystemInstructionsWithSigningModeAt(payload, strictMode, cchSigning, version, entrypoint, workload, time.Now(), false, "", "") | ||
| } | ||
|
|
||
| // isClaudeFable51Model reports whether the model is specifically Fable 5.1 / Mythos 5.1, | ||
| // matching native Claude Code 2.1.258 family/major/minor checks (AFo = {major:5, minor:1}). | ||
| func isClaudeFable51Model(model string) bool { | ||
| m := strings.ToLower(strings.TrimSpace(model)) | ||
| return strings.Contains(m, "fable-5-1") || strings.Contains(m, "fable-5.1") || strings.Contains(m, "mythos-5-1") || strings.Contains(m, "mythos-5.1") | ||
| } | ||
|
|
||
| func checkSystemInstructionsWithSigningModeAt(payload []byte, strictMode bool, cchSigning bool, version, entrypoint, workload string, now time.Time) []byte { | ||
| func checkSystemInstructionsWithSigningModeAt( | ||
| payload []byte, | ||
| strictMode bool, | ||
| cchSigning bool, | ||
| version, entrypoint, workload string, | ||
| now time.Time, | ||
| isSubagent bool, | ||
| prevReq, promptID string, | ||
| ) []byte { | ||
| system := gjson.GetBytes(payload, "system") | ||
| messageText := claudeBillingFingerprintMessageText(payload) | ||
|
|
||
| billingText := generateBillingHeader(cchSigning, version, messageText, entrypoint, workload) | ||
| billingText := generateBillingHeader(cchSigning, version, messageText, entrypoint, workload, isSubagent, prevReq, promptID) | ||
| billingBlock := buildTextBlock(billingText, nil) | ||
| agentBlock := buildTextBlock(claudeCodeCLIIdentity, &claudeCodeCacheControl) | ||
| payload, _ = sjson.SetRawBytes(payload, "system", []byte("["+billingBlock+","+agentBlock+"]")) | ||
|
|
||
| systemBlocks := []string{billingBlock, agentBlock} | ||
| model := strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "model").String())) | ||
| if isClaudeFable51Model(model) && !helps.IsClaudeProbeOrHelperRequest(payload) { | ||
| systemBlocks = append(systemBlocks, buildTextBlock(claudeCodeFableReportingOutcomes, nil)) | ||
| } | ||
| payload, _ = sjson.SetRawBytes(payload, "system", []byte("["+strings.Join(systemBlocks, ",")+"]")) | ||
| if strictMode { | ||
| return injectClaudeCodeCurrentDate(payload, now) | ||
| } | ||
|
|
@@ -701,6 +763,119 @@ func reconcileClaudeCodeSystemPlacementAfterPayload(payload []byte, state claude | |
| return prependClaudeSystemRemindersToFirstUserMessage(updated, state.texts) | ||
| } | ||
|
|
||
| type claudeCodeFableState struct { | ||
| injectedFallbacks bool | ||
| injectedDisplay bool | ||
| } | ||
|
|
||
| func captureClaudeCodeFableState(before, after []byte, cloaked bool) claudeCodeFableState { | ||
| if !cloaked || len(before) == 0 || len(after) == 0 { | ||
| return claudeCodeFableState{} | ||
| } | ||
| return claudeCodeFableState{ | ||
| injectedFallbacks: !gjson.GetBytes(before, "fallbacks").Exists() && gjson.GetBytes(after, "fallbacks").Exists(), | ||
| injectedDisplay: !gjson.GetBytes(before, "thinking.display").Exists() && gjson.GetBytes(after, "thinking.display").Exists(), | ||
| } | ||
| } | ||
|
|
||
| func payloadRulesTouchPath(cfg *config.Config, path string) bool { | ||
| if cfg == nil { | ||
| return false | ||
| } | ||
| for _, r := range cfg.Payload.Override { | ||
| if _, ok := r.Params[path]; ok { | ||
| return true | ||
| } | ||
| } | ||
| for _, r := range cfg.Payload.Default { | ||
| if _, ok := r.Params[path]; ok { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| // reconcileClaudeCodeFableModelAfterPayload reconciles model-specific additions | ||
| // (Opus fallback, thinking.display=updates, and # Reporting outcomes system block) | ||
| // if payload rules rewrite the request model between Fable 5.1 and non-Fable models. | ||
| func reconcileClaudeCodeFableModelAfterPayload( | ||
| body []byte, | ||
| bodyBeforePayload []byte, | ||
| fableState claudeCodeFableState, | ||
| cfg *config.Config, | ||
| cloaked bool, | ||
| isProbeOrHelper bool, | ||
| ) []byte { | ||
| if !cloaked || isProbeOrHelper || len(body) == 0 { | ||
| return body | ||
| } | ||
| model := strings.ToLower(strings.TrimSpace(gjson.GetBytes(body, "model").String())) | ||
|
|
||
| if isClaudeFable51Model(model) { | ||
| // Non-Fable rewritten to Fable 5.1 (or original Fable 5.1): attach Fable additions | ||
| if !gjson.GetBytes(body, "fallbacks").Exists() { | ||
| body, _ = sjson.SetRawBytes(body, "fallbacks", []byte(`[{"model":"claude-opus-5"}]`)) | ||
| } | ||
| if gjson.GetBytes(body, "thinking").Exists() { | ||
| thinkingType := gjson.GetBytes(body, "thinking.type").String() | ||
| if thinkingType == "adaptive" && !gjson.GetBytes(body, "thinking.display").Exists() { | ||
| body, _ = sjson.SetBytes(body, "thinking.display", "updates") | ||
| } | ||
| } | ||
| system := gjson.GetBytes(body, "system") | ||
| if system.IsArray() { | ||
| hasReporting := false | ||
| for _, blk := range system.Array() { | ||
| if strings.Contains(blk.Get("text").String(), "Reporting outcomes") { | ||
| hasReporting = true | ||
| break | ||
| } | ||
| } | ||
| if !hasReporting { | ||
| blocks := make([]string, 0, len(system.Array())+1) | ||
| for _, blk := range system.Array() { | ||
| blocks = append(blocks, blk.Raw) | ||
| } | ||
| blocks = append(blocks, buildTextBlock(claudeCodeFableReportingOutcomes, nil)) | ||
| body, _ = sjson.SetRawBytes(body, "system", []byte("["+strings.Join(blocks, ",")+"]")) | ||
| } | ||
| } | ||
| return body | ||
| } | ||
|
|
||
| // Target model is Non-Fable 5.1: | ||
| // Only delete fallbacks if CPA automatically injected it and payload rule did NOT explicitly configure/modify it | ||
| payloadRuleTouchedFallbacks := payloadRulesTouchPath(cfg, "fallbacks") || | ||
| (gjson.GetBytes(body, "fallbacks").Raw != gjson.GetBytes(bodyBeforePayload, "fallbacks").Raw) | ||
| if fableState.injectedFallbacks && !payloadRuleTouchedFallbacks { | ||
| body, _ = sjson.DeleteBytes(body, "fallbacks") | ||
| } | ||
|
|
||
| payloadRuleTouchedDisplay := payloadRulesTouchPath(cfg, "thinking.display") || | ||
| (gjson.GetBytes(body, "thinking.display").Raw != gjson.GetBytes(bodyBeforePayload, "thinking.display").Raw) | ||
| if fableState.injectedDisplay && !payloadRuleTouchedDisplay { | ||
| body, _ = sjson.DeleteBytes(body, "thinking.display") | ||
| } | ||
|
|
||
| // Remove Reporting outcomes if present in system | ||
| system := gjson.GetBytes(body, "system") | ||
| if system.IsArray() { | ||
| blocks := make([]string, 0, len(system.Array())) | ||
| removed := false | ||
| for _, blk := range system.Array() { | ||
| if strings.Contains(blk.Get("text").String(), "Reporting outcomes") { | ||
| removed = true | ||
| continue | ||
| } | ||
| blocks = append(blocks, blk.Raw) | ||
| } | ||
| if removed { | ||
| body, _ = sjson.SetRawBytes(body, "system", []byte("["+strings.Join(blocks, ",")+"]")) | ||
| } | ||
| } | ||
| return body | ||
| } | ||
|
|
||
| // claudeCodeLocalDate reproduces Claude Code 2.1.220's wcs() helper: | ||
| // new Date(), local calendar fields, and zero-padded YYYY-MM-DD components. | ||
| func claudeCodeLocalDate(now time.Time) string { | ||
|
|
@@ -1026,7 +1201,75 @@ func applyCloaking( | |
|
|
||
| billingVersion := helps.DefaultClaudeVersion(cfg) | ||
| workload := getWorkloadFromContext(ctx) | ||
| payload = checkSystemInstructionsWithSigningModeAt(payload, settings.strictMode, cchSigning, billingVersion, "cli", workload, claudeCodeCurrentTime(cfg, auth)) | ||
|
|
||
| isProbeOrHelper := helps.IsClaudeProbeOrHelperRequest(payload) | ||
| isSubagent := false | ||
| prevReq := "" | ||
| promptID := "" | ||
| if !isProbeOrHelper { | ||
| incomingHeaders := resolveIncomingClaudeHeaders(ctx, helps.IncomingHeadersFromContext(ctx)) | ||
| isSubagent = helps.IsClaudeSubagentRequest(incomingHeaders, payload) | ||
| existingPrevReq, existingPromptID := helps.ExtractClaudeBillingTags(payload) | ||
|
|
||
| sessionID := helps.ClaudeSessionIDFromContext(ctx) | ||
| if sessionID == "" && auth != nil { | ||
| sessionID = helps.ClaudeAgentSessionUUIDForRequest(incomingHeaders, payload, payload, confirmedClaudeCode) | ||
| } | ||
|
|
||
| if sessionID != "" && auth != nil { | ||
| credIdentity := claudeDiagnosticsCredentialIdentity(auth) | ||
| isNewTurn := helps.IsClaudeNewPromptTurn(payload) | ||
| continuityKey, seq, prevMsgID, storedPrevReq, storedPromptID := helps.BeginClaudeContinuity(credIdentity, sessionID, isNewTurn, existingPromptID) | ||
|
|
||
| if existingPromptID != "" { | ||
| promptID = existingPromptID | ||
| } else { | ||
| promptID = storedPromptID | ||
| } | ||
| if existingPrevReq != "" { | ||
| prevReq = existingPrevReq | ||
| } else { | ||
| prevReq = storedPrevReq | ||
| } | ||
|
|
||
| if continuityCtx := helps.ClaudeContinuityContextFromContext(ctx); continuityCtx != nil { | ||
| continuityCtx.Key = continuityKey | ||
| continuityCtx.Sequence = seq | ||
| continuityCtx.PreviousMessageID = prevMsgID | ||
| continuityCtx.PreviousRequestID = prevReq | ||
| continuityCtx.PromptID = promptID | ||
| continuityCtx.Initialized = true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| payload = checkSystemInstructionsWithSigningModeAt( | ||
| payload, | ||
| settings.strictMode, | ||
| cchSigning, | ||
| billingVersion, | ||
| "cli", | ||
| workload, | ||
| claudeCodeCurrentTime(cfg, auth), | ||
| isSubagent, | ||
| prevReq, | ||
| promptID, | ||
| ) | ||
|
|
||
| // In native Claude Code 2.1.258, claude-fable-5-1 requests carry: | ||
| // "fallbacks": [{"model": "claude-opus-5"}] | ||
| model := strings.ToLower(strings.TrimSpace(gjson.GetBytes(payload, "model").String())) | ||
| if isClaudeFable51Model(model) && !isProbeOrHelper { | ||
|
Comment on lines
+1261
to
+1262
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an operator uses a payload override to change Useful? React with 👍 / 👎. |
||
| if !gjson.GetBytes(payload, "fallbacks").Exists() { | ||
| payload, _ = sjson.SetRawBytes(payload, "fallbacks", []byte(`[{"model":"claude-opus-5"}]`)) | ||
| } | ||
| if gjson.GetBytes(payload, "thinking").Exists() { | ||
| thinkingType := gjson.GetBytes(payload, "thinking.type").String() | ||
| if thinkingType == "adaptive" && !gjson.GetBytes(payload, "thinking.display").Exists() { | ||
| payload, _ = sjson.SetBytes(payload, "thinking.display", "updates") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Claude-Code-CLI fingerprint identity (real OAuth or fingerprint-profile=claude-code-cli) | ||
| // is applied later through the shared ApplyClaudeCredentialMetadata path. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This scan treats a path as touched whenever any configured rule contains it, even when that rule's model, protocol, header, or body predicates do not match the current request. For example, if one rule rewrites Fable 5.1 to Sonnet and an unrelated model's rule defines
fallbacks, reconciliation preserves the CPA-injected Opus fallback on the Sonnet request, retaining the server-side-fallback beta and allowing an unintended Opus execution. Track whether a matching rule actually wrote the path; the same false ownership applies tothinking.display.Useful? React with 👍 / 👎.