Skip to content
Open
265 changes: 254 additions & 11 deletions internal/runtime/executor/claude_executor_cloaking.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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", "")
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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
Comment on lines +785 to +787

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope Fable ownership checks to applied payload rules

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 to thinking.display.

Useful? React with 👍 / 👎.

}
}
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 {
Expand Down Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply Fable additions after payload model overrides

When an operator uses a payload override to change model, this decision is made from the pre-override model because both Execute and ExecuteStream call ApplyPayloadConfigWithRequestTracked only after applyCloaking. Rewriting Fable 5.1 to another model therefore leaves its Opus fallback, thinking.display, and reporting system block attached, allowing an unexpected Opus fallback; rewriting another model to Fable 5.1 omits all of those required additions. Reconcile these model-dependent fields after payload rules have established the final upstream model.

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.
Expand Down
29 changes: 21 additions & 8 deletions internal/runtime/executor/claude_executor_diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,22 @@ import (
type claudeDiagnosticsRequestState struct {
key string
sequence uint64
promptID string
}

func injectClaudeDiagnostics(body []byte, auth *cliproxyauth.Auth, sessionID string) ([]byte, claudeDiagnosticsRequestState) {
key, sequence, previousMessageID := helps.BeginClaudeDiagnostics(claudeDiagnosticsCredentialIdentity(auth), sessionID)
key, sequence, previousMessageID, _, promptID := helps.BeginClaudeContinuity(claudeDiagnosticsCredentialIdentity(auth), sessionID, false, "")
return injectClaudeDiagnosticsWithState(body, key, sequence, previousMessageID, promptID)
}

func injectClaudeDiagnosticsWithState(body []byte, key string, sequence uint64, previousMessageID string, promptIDs ...string) ([]byte, claudeDiagnosticsRequestState) {
if key == "" {
return body, claudeDiagnosticsRequestState{}
}
promptID := ""
if len(promptIDs) > 0 {
promptID = promptIDs[0]
}
value := `{"previous_message_id":null}`
if previousMessageID != "" {
value = `{"previous_message_id":` + marshalJSONStringWithoutHTMLEscape(previousMessageID) + `}`
Expand All @@ -29,7 +38,7 @@ func injectClaudeDiagnostics(body []byte, auth *cliproxyauth.Auth, sessionID str
if diagnostics := gjson.GetBytes(body, "diagnostics"); diagnostics.Exists() {
updated, errSet := sjson.SetRawBytes(body, "diagnostics", []byte(value))
if errSet == nil {
return updated, claudeDiagnosticsRequestState{key: key, sequence: sequence}
return updated, claudeDiagnosticsRequestState{key: key, sequence: sequence, promptID: promptID}
}
}
if contextManagement := gjson.GetBytes(body, "context_management"); contextManagement.Exists() {
Expand All @@ -41,14 +50,22 @@ func injectClaudeDiagnostics(body []byte, auth *cliproxyauth.Auth, sessionID str
updated = append(updated, `,"diagnostics":`...)
updated = append(updated, value...)
updated = append(updated, body[insertAt:]...)
return updated, claudeDiagnosticsRequestState{key: key, sequence: sequence}
return updated, claudeDiagnosticsRequestState{key: key, sequence: sequence, promptID: promptID}
}
}
updated, errSet := sjson.SetRawBytes(body, "diagnostics", []byte(value))
if errSet != nil {
return body, claudeDiagnosticsRequestState{}
}
return updated, claudeDiagnosticsRequestState{key: key, sequence: sequence}
return updated, claudeDiagnosticsRequestState{key: key, sequence: sequence, promptID: promptID}
}

func commitClaudeContinuity(state claudeDiagnosticsRequestState, messageID, requestID string) {
helps.CommitClaudeContinuity(state.key, state.sequence, messageID, requestID, state.promptID)
}

func commitClaudeDiagnostics(state claudeDiagnosticsRequestState, messageID string) {
commitClaudeContinuity(state, messageID, "")
}

func claudeDiagnosticsCredentialIdentity(auth *cliproxyauth.Auth) string {
Expand All @@ -71,10 +88,6 @@ func claudeDiagnosticsCredentialIdentity(auth *cliproxyauth.Auth) string {
return ""
}

func commitClaudeDiagnostics(state claudeDiagnosticsRequestState, messageID string) {
helps.CommitClaudeDiagnostics(state.key, state.sequence, messageID)
}

func claudeMessageIDFromResponse(data []byte) string {
return strings.TrimSpace(gjson.GetBytes(data, "id").String())
}
Expand Down
Loading
Loading