@@ -157,10 +157,13 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
157157 extraBetas , body = extractAndRemoveBetas (body )
158158 bodyForTranslation := body
159159 bodyForUpstream := body
160- if isClaudeOAuthToken (apiKey ) && ! auth .ToolPrefixDisabled () {
160+ oauthToken := isClaudeOAuthToken (apiKey )
161+ if oauthToken && ! auth .ToolPrefixDisabled () {
161162 bodyForUpstream = applyClaudeToolPrefix (body , claudeToolPrefix )
162163 }
163- if experimentalCCHSigningEnabled (e .cfg , auth ) {
164+ // Enable cch signing by default for OAuth tokens (not just experimental flag).
165+ // Claude Code always computes cch; missing or invalid cch is a detectable fingerprint.
166+ if oauthToken || experimentalCCHSigningEnabled (e .cfg , auth ) {
164167 bodyForUpstream = signAnthropicMessagesBody (bodyForUpstream )
165168 }
166169
@@ -325,10 +328,12 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
325328 extraBetas , body = extractAndRemoveBetas (body )
326329 bodyForTranslation := body
327330 bodyForUpstream := body
328- if isClaudeOAuthToken (apiKey ) && ! auth .ToolPrefixDisabled () {
331+ oauthToken := isClaudeOAuthToken (apiKey )
332+ if oauthToken && ! auth .ToolPrefixDisabled () {
329333 bodyForUpstream = applyClaudeToolPrefix (body , claudeToolPrefix )
330334 }
331- if experimentalCCHSigningEnabled (e .cfg , auth ) {
335+ // Enable cch signing by default for OAuth tokens (not just experimental flag).
336+ if oauthToken || experimentalCCHSigningEnabled (e .cfg , auth ) {
332337 bodyForUpstream = signAnthropicMessagesBody (bodyForUpstream )
333338 }
334339
@@ -1291,55 +1296,101 @@ func checkSystemInstructionsWithSigningMode(payload []byte, strictMode bool, exp
12911296 // Including any cache_control here creates an intra-system TTL ordering violation
12921297 // when the client's system blocks use ttl='1h' (prompt-caching-scope-2026-01-05 beta
12931298 // forbids 1h blocks after 5m blocks, and a no-TTL block defaults to 5m).
1294- agentBlock := `{"type":"text","text":"You are a Claude agent, built on Anthropic's Claude Agent SDK."}`
1295-
1296- if strictMode {
1297- // Strict mode: billing header + agent identifier only
1298- result := "[" + billingBlock + "," + agentBlock + "]"
1299- payload , _ = sjson .SetRawBytes (payload , "system" , []byte (result ))
1300- return payload
1301- }
1299+ // Use Claude Code identity prefix for interactive CLI mode.
1300+ // Real Claude Code uses "You are Claude Code, Anthropic's official CLI for Claude."
1301+ // when running in interactive mode (the most common case).
1302+ agentBlock := `{"type":"text","text":"You are Claude Code, Anthropic's official CLI for Claude."}`
13021303
1303- // Non-strict mode: billing header + agent identifier + user system messages
13041304 // Skip if already injected
13051305 firstText := gjson .GetBytes (payload , "system.0.text" ).String ()
13061306 if strings .HasPrefix (firstText , "x-anthropic-billing-header:" ) {
13071307 return payload
13081308 }
13091309
1310- result := "[" + billingBlock + "," + agentBlock
1310+ // system[] only keeps billing header + agent identifier.
1311+ // User system instructions are moved to the first user message to avoid
1312+ // Anthropic's content-based system prompt validation (extra usage detection).
1313+ systemResult := "[" + billingBlock + "," + agentBlock + "]"
1314+ payload , _ = sjson .SetRawBytes (payload , "system" , []byte (systemResult ))
1315+
1316+ // Collect user system instructions and prepend to first user message
1317+ var userSystemParts []string
13111318 if system .IsArray () {
13121319 system .ForEach (func (_ , part gjson.Result ) bool {
13131320 if part .Get ("type" ).String () == "text" {
1314- // Add cache_control to user system messages if not present.
1315- // Do NOT add ttl — let it inherit the default (5m) to avoid
1316- // TTL ordering violations with the prompt-caching-scope-2026-01-05 beta.
1317- partJSON := part .Raw
1318- if ! part .Get ("cache_control" ).Exists () {
1319- updated , _ := sjson .SetBytes ([]byte (partJSON ), "cache_control.type" , "ephemeral" )
1320- partJSON = string (updated )
1321+ txt := strings .TrimSpace (part .Get ("text" ).String ())
1322+ if txt != "" {
1323+ userSystemParts = append (userSystemParts , txt )
13211324 }
1322- result += "," + partJSON
13231325 }
13241326 return true
13251327 })
1326- } else if system .Type == gjson .String && system .String () != "" {
1327- partJSON := `{"type":"text","cache_control":{"type":"ephemeral"}}`
1328- updated , _ := sjson .SetBytes ([]byte (partJSON ), "text" , system .String ())
1329- partJSON = string (updated )
1330- result += "," + partJSON
1328+ } else if system .Type == gjson .String && strings .TrimSpace (system .String ()) != "" {
1329+ userSystemParts = append (userSystemParts , strings .TrimSpace (system .String ()))
1330+ }
1331+
1332+ if ! strictMode && len (userSystemParts ) > 0 {
1333+ combined := strings .Join (userSystemParts , "\n \n " )
1334+ payload = prependToFirstUserMessage (payload , combined )
1335+ }
1336+
1337+ return payload
1338+ }
1339+
1340+ // prependToFirstUserMessage prepends text content to the first user message.
1341+ // This avoids putting non-Claude-Code system instructions in system[] which
1342+ // triggers Anthropic's extra usage billing for OAuth-proxied requests.
1343+ func prependToFirstUserMessage (payload []byte , text string ) []byte {
1344+ messages := gjson .GetBytes (payload , "messages" )
1345+ if ! messages .Exists () || ! messages .IsArray () {
1346+ return payload
1347+ }
1348+
1349+ // Find the first user message index
1350+ firstUserIdx := - 1
1351+ messages .ForEach (func (idx , msg gjson.Result ) bool {
1352+ if msg .Get ("role" ).String () == "user" {
1353+ firstUserIdx = int (idx .Int ())
1354+ return false
1355+ }
1356+ return true
1357+ })
1358+
1359+ if firstUserIdx < 0 {
1360+ return payload
1361+ }
1362+
1363+ prefixBlock := fmt .Sprintf (`<system-reminder>
1364+ As you answer the user's questions, you can use the following context from the system:
1365+ %s
1366+
1367+ IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
1368+ </system-reminder>
1369+ ` , text )
1370+
1371+ contentPath := fmt .Sprintf ("messages.%d.content" , firstUserIdx )
1372+ content := gjson .GetBytes (payload , contentPath )
1373+
1374+ if content .IsArray () {
1375+ newBlock := fmt .Sprintf (`{"type":"text","text":%q}` , prefixBlock )
1376+ existing := content .Raw
1377+ newArray := "[" + newBlock + "," + existing [1 :]
1378+ payload , _ = sjson .SetRawBytes (payload , contentPath , []byte (newArray ))
1379+ } else if content .Type == gjson .String {
1380+ newText := prefixBlock + content .String ()
1381+ payload , _ = sjson .SetBytes (payload , contentPath , newText )
13311382 }
1332- result += "]"
13331383
1334- payload , _ = sjson .SetRawBytes (payload , "system" , []byte (result ))
13351384 return payload
13361385}
13371386
13381387// applyCloaking applies cloaking transformations to the payload based on config and client.
13391388// Cloaking includes: system prompt injection, fake user ID, and sensitive word obfuscation.
13401389func applyCloaking (ctx context.Context , cfg * config.Config , auth * cliproxyauth.Auth , payload []byte , model string , apiKey string ) []byte {
13411390 clientUserAgent := getClientUserAgent (ctx )
1342- useExperimentalCCHSigning := experimentalCCHSigningEnabled (cfg , auth )
1391+ // Enable cch signing for OAuth tokens by default (not just experimental flag).
1392+ oauthToken := isClaudeOAuthToken (apiKey )
1393+ useCCHSigning := oauthToken || experimentalCCHSigningEnabled (cfg , auth )
13431394
13441395 // Get cloak config from ClaudeKey configuration
13451396 cloakCfg := resolveClaudeKeyCloakConfig (cfg , auth )
@@ -1376,7 +1427,7 @@ func applyCloaking(ctx context.Context, cfg *config.Config, auth *cliproxyauth.A
13761427 billingVersion := helps .DefaultClaudeVersion (cfg )
13771428 entrypoint := parseEntrypointFromUA (clientUserAgent )
13781429 workload := getWorkloadFromContext (ctx )
1379- payload = checkSystemInstructionsWithSigningMode (payload , strictMode , useExperimentalCCHSigning , billingVersion , entrypoint , workload )
1430+ payload = checkSystemInstructionsWithSigningMode (payload , strictMode , useCCHSigning , billingVersion , entrypoint , workload )
13801431 }
13811432
13821433 // Inject fake user ID
0 commit comments