Skip to content

Review only: HOUSE-79 upstream v8.0.44, part 4 of 13: claude executor, thinking, signatures - #17

Closed
ericlitman wants to merge 1 commit into
review/house79/base-04from
review/house79/chunk-04
Closed

ericlitman wants to merge 1 commit into
review/house79/base-04from
review/house79/chunk-04

Conversation

@ericlitman

@ericlitman ericlitman commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Review only. Do not merge. This PR is one part of the review of #13, the merge of upstream v8.0.44 into the fork. That PR's diff (966 files) is too large for one review round.

  • This part covers: claude executor, thinking, signatures.
  • The head tree is identical to Merge upstream v8.0.44 into the fork (HOUSE-79) #13 head tree (c88d701f7bc0894cde517ca7f931f5c05749cd74). The base is that tree with only this part's files restored to main (c265779f), so the diff is this part's files alone and the surrounding code is the real final code.
  • The 13 parts together cover every production file the sync changes (410 paths). Tests, golden data, generated model catalogs, images, docs and go.sum are in no part's diff.
  • The code is upstream's, except where our fork's soonest-reset selector, quota observations, quota reserves, usage reads and Terra injection touch it. Review it as code we are about to deploy to the proxy every one of our agents uses: report defects that would affect our deployment.

Linear: HOUSE-79

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@mergify

mergify Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@unfret

unfret Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🛑 9 blocking findings

🔴 Critical · Reject truncated summaries before sealing compaction

🟡 Medium · Validate the finalized count-token body

🟡 Medium · Keep response parsing aligned with the configured stream value

🟡 Medium · Do not overwrite a parent message's alias state

🟡 Medium · Lift token-count betas after payload finalization

🟡 Medium · Validate token-count model compatibility after payload rules

🟡 Medium · Reset Gemini function-call groups across server-tool parts

🟡 Medium · Do not delete prompt-cache configuration after payload rules

🔵 Low · Place alias-cache support under helps

Walkthrough

This review-only PR is part 4 of 13 of the upstream v8.0.44 merge (#13). It isolates the Claude executor, thinking, and signature-validation changes and is not meant to be merged. The main behavior changes are: Claude now handles Responses compaction itself instead of returning 501, user payload rules move to a single pass on the fully prepared upstream body, and cloaking gains an explicit "never/uncloak" mode. The slice also adds explicit prompt-cache mode, session-scoped date pins, keeping OAuth MCP alias state across thread continuations, and new signature families (Antigravity Q-form CAQS and Meta Muse).

Area What changes Review notes
Responses compaction Claude executes /responses/compact and compaction triggers by running a summary request and sealing the result into a capsule. It also expands incoming capsules and rejects unrecognized ones. Reject truncated summaries before sealing compaction
Request pipeline and payload rules Execute and stream paths apply payload rules once, after cloaking, cache-control, CCH placeholder and preserved-thinking binding. After the rules run, they lift betas, strip prompt_cache_options and sign CCH. Count-tokens adds a payload finalizer and the same model and tool-choice normalization. Keep response parsing aligned with the configured stream value
Do not delete prompt-cache configuration after payload rules
Validate the finalized count-token body
Lift token-count betas after payload finalization
Validate token-count model compatibility after payload rules
OAuth tool alias state A bounded in-memory store, shared by the executor's per-request copies, remembers MCP alias maps by message ID. Thread continuations without tools can then restore names, and they return 404 when no state is found. Do not overwrite a parent message's alias state
Place alias-cache support under helps
Cloaking, headers and cache control The never/uncloak mode strips Claude Code identity blocks, metadata, headers and betas. For OAuth, caller system prompts stay out of the top-level system field. Explicit prompt-cache mode preserves caller cache_control. Empty or thinking blocks no longer host breakpoints. Session date pins can outlive continuity. Beta strip/append and client-request-id overrides are added, and streams that close before message_stop now error.
Signatures and thinking Adds Antigravity Q-form CAQS validation, which native Claude endpoints drop. Also adds Meta Muse signatures and router-suffix handling for GPT, Grok and Meta. The Gemini first-function-call rule now applies per parallel group. Thinking changes use model-specific disable types such as between_tools, fall back to the lowest level on adaptive-only models, and let plugin appliers take precedence. Reset Gemini function-call groups across server-tool parts
Rate limits Reset deadlines longer than 7 days and overage billing boundaries are ignored. A body that is exactly a per-minute rate-limit message keeps its short Retry-After instead of the unified reset.
Since the last review
  • New: Place alias-cache support under helps
  • New: Reject truncated summaries before sealing compaction
  • New: Validate the finalized count-token body
  • New: Keep response parsing aligned with the configured stream value
  • New: Do not overwrite a parent message's alias state
  • New: Lift token-count betas after payload finalization
  • New: Validate token-count model compatibility after payload rules
  • New: Reset Gemini function-call groups across server-tool parts
  • New: Do not delete prompt-cache configuration after payload rules
Repair prompt

Read the Unfret check run for mobilyze-llc/CLIProxyAPI#17 at head b4cadfd13907b3b821e0f96d9d1da79c91b8b459 (run panel:b4cadfd13907b3b821e0f96d9d1da79c91b8b459:2xlcy81hGYg:40aevBM2q28, check). Use the check's App id and GET /repos/mobilyze-llc/CLIProxyAPI/check-runs/122312237568; its output.text contains the structured result. Compare it with the current code and head, verify that each listed mandatory finding still applies, repair only those that do, honor the repository constraints, and validate the changes. Leave advisories and accepted risks alone. A manually resolved thread is not a waiver.

Details
Lane / stage Model Effort Outcome Duration
broad-gpt-5.6-sol-xhigh / review openai/gpt-5.6-sol → gpt-5.6-sol · primary xhigh succeeded 10 min 48 s
broad-opus-5.5-max / review Not recorded Not recorded failed Not recorded
focal-gpt-5.6-sol-xhigh copy / review openai/gpt-6.1-sol → gpt-6.1-sol · primary xhigh succeeded 13 min 3 s
focal-gpt-5.6-sol-xhigh copy 2 / review openai/gpt-6.1-sol → gpt-6.1-sol · primary xhigh succeeded 14 min 26 s
judge / judge openai/gpt-6-astra → gpt-6-astra · primary high succeeded 2 min 23 s

First review of this pull request.
3 reviewers finished.
Commit b4cadfd.
Check run.
Run: GET /unfret/run/panel:b4cadfd13907b3b821e0f96d9d1da79c91b8b459:2xlcy81hGYg:40aevBM2q28.

@unfret review · @unfret status · @unfret help

@unfret unfret Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 9 blocking findings
🔴 Critical · Reject truncated summaries before sealing compaction
🟡 Medium · Validate the finalized count-token body
🟡 Medium · Keep response parsing aligned with the configured stream value
🟡 Medium · Do not overwrite a parent message's alias state
🟡 Medium · Lift token-count betas after payload finalization
🟡 Medium · Validate token-count model compatibility after payload rules
🟡 Medium · Reset Gemini function-call groups across server-tool parts
🟡 Medium · Do not delete prompt-cache configuration after payload rules
🔵 Low · Place alias-cache support under helps
Check

Comment on lines +18 to +22
type claudeOAuthToolAliasStore struct {
mu sync.Mutex
entries map[string]map[string]string
order []string
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low · Place alias-cache support under helps

AGENTS.md requires executor/ to contain executors and their unit tests only, with helper/supporting files under executor/helps/. This new file adds a supporting alias-cache implementation, including storage, locking, cloning, and eviction, directly under executor/. Move the cache-support portion under helps to satisfy the mandatory directory rule. Also reported for this defect: - Place the new alias-store support code under helps (internal/runtime/executor/claude_executor_tool_state.go:18-22): AGENTS.md requires helper/supporting files to live under internal/runtime/executor/helps/, reserving the executor directory for executors and their unit tests. This new file introduces alias-cache storage, cloning, key construction, and error helpers directly in the executor directory. Move the supporting implementation under helps while retaining executor-specific integration in executor files.

Comment on lines +63 to +69
capsule, errSeal := helps.SealAntigravityCompaction(summaryText, baseModel)
if errSeal != nil {
return resp, fmt.Errorf("seal compaction capsule: %w", errSeal)
}

inputTokens, outputTokens, totalTokens, cachedTokens := claudeCompactionResponsesUsage(summaryResp.Payload)
respPayload := helps.BuildAntigravityCompactionResponse(baseModel, capsule, inputTokens, outputTokens, totalTokens)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Critical · Reject truncated summaries before sealing compaction

An HTTP 200 summary response can contain nonempty text with stop_reason="max_tokens". This path seals that partial text and returns status="completed" without checking the stop reason. Reproducing with max_output_tokens=1 produced a successful capsule containing only "The". A client replacing its history with that capsule loses the conversation context. Both compaction entrypoints use this path. Also reported for this defect: - Reject truncated summaries before sealing compaction (internal/runtime/executor/claude_executor_compaction.go:63-69): A compaction request retaining max_output_tokens:1 sends max_tokens:1 to Claude. If Claude returns nonempty text with stop_reason:"max_tokens", this code seals that truncated prefix and reports the compaction as completed. Reproduced with a capsule containing only "The". A client replacing its history with the successful compaction result loses the conversation context. Check summary completion before sealing.

@@ -217,6 +256,8 @@ func (e *ClaudeExecutor) countTokensUpstream(ctx context.Context, auth *cliproxy
if errMidSystem := validateClaudeMidSystemMessageModel(body, confirmedClaudeCode, directAnthropic); errMidSystem != nil {
return cliproxyexecutor.Response{}, errMidSystem
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Validate the finalized count-token body

This finalizer runs after validateClaudeMidSystemMessageModel, so the guard does not inspect the body actually sent. For example, a payload rule that changes a cloaked request from a modern model to a legacy model leaves the inserted mid-conversation system turn unvalidated, and Anthropic returns an upstream 400. Read-only validation needs to run after the final payload rules.

Comment on lines +249 to +253
bodyForUpstream, touchedPayloadPaths = helps.ApplyPayloadConfigWithTrackedPaths(
e.cfg, baseModel, to.String(), from.String(), "", bodyForUpstream, stripTranslatedDefaultMaxTokens(from, originalPayload, originalTranslated),
helps.PayloadRequestedModel(opts, req.Model), helps.PayloadRequestPath(opts), opts.Headers,
"diagnostics",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Keep response parsing aligned with the configured stream value

Moving payload rules after stream selection lets a stream:false override reach upstream while Execute still uses upstreamStream=true for Chat/Responses response translation. Anthropic then returns JSON, but validateClaudeStreamingResponse parses it as SSE and returns a 502 "upstream returned empty stream response". This was reproduced with a successful JSON upstream response. Preserve payload-rule authority while selecting the parser from the actual upstream response mode. Also reported for this defect: - Keep response parsing aligned with configured stream mode (internal/runtime/executor/claude_executor_execute.go:249-253): For Chat or Responses requests, Execute fixes upstreamStream=true before applying payload rules. Moving these rules after the stream-setting guard lets an override such as stream=false reach upstream, but headers and response parsing still use upstreamStream=true. In a reproduction, upstream returned a valid JSON Messages response and Execute rejected it with HTTP 502, "upstream returned empty stream response".

Comment on lines +124 to +128
keys := claudeOAuthToolAliasKeys(payload, messageID)
if len(keys) == 0 {
return
}
e.claudeOAuthToolAliasStore().save(keys, aliases)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Do not overwrite a parent message's alias state

The save keys include thread.previous_message_id as well as the newly returned message ID. If parent m1 declares Read and a continuation replaces its tools with Write, completing that continuation overwrites m1's alias map with Write's map. A subsequent branch from m1 without declarations then loads the wrong map and forwards Read's generated MCP name instead of restoring Read.

Comment on lines +259 to +260
body = helps.NewPayloadFinalizer(e.cfg, e.Identifier(), baseModel, to.String(), "", originalTranslatedForPayload, req, opts)(body)
body = stripPromptCacheOptions(body)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Lift token-count betas after payload finalization

countTokensUpstream extracts betas before this newly added finalizer. A payload rule that supplies betas therefore leaves the proxy-only field in the outgoing JSON while extraBetas retains the earlier value. For example, overriding betas with advisor-tool-2026-03-01 sends that array in the body without the corresponding header beta, causing an upstream 400 for the unsupported betas field. The Messages paths perform this transport extraction after payload rules. Also reported for this defect: - Extract count-token betas after payload rules (internal/runtime/executor/claude_executor_tokens.go:258): The count-token path extracts betas before this newly added payload finalizer. If a matching payload rule adds or overrides betas, the value remains as an unsupported JSON body field and is never copied to Anthropic-Beta, so Anthropic rejects the count request instead of enabling the configured beta. - Extract token-count betas after applying payload rules (internal/runtime/executor/claude_executor_tokens.go:259-260): countTokensUpstream extracts and removes betas at line 220, before this newly added finalizer. A payload rule setting betas therefore leaves the array in the outgoing JSON and omits its values from Anthropic-Beta. Direct Anthropic rejects the extra body field instead of counting tokens. The Messages paths correctly extract betas after their final barrier; token counting needs the same ordering.

Also affects:

if errMidSystem := validateClaudeMidSystemMessageModel(body, confirmedClaudeCode, directAnthropic); errMidSystem != nil {
return cliproxyexecutor.Response{}, errMidSystem
}
body = helps.NewPayloadFinalizer(e.cfg, e.Identifier(), baseModel, to.String(), "", originalTranslatedForPayload, req, opts)(body)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Validate token-count model compatibility after payload rules

The new finalizer runs after validateClaudeMidSystemMessageModel, so that guard no longer checks the body being sent. A rule changing a modern model with a system turn to a legacy model bypasses local request-scoped rejection and sends a guaranteed upstream 400. Conversely, a rule filtering incompatible system turns cannot repair the request because validation returns before the rule executes; this premature rejection was reproduced. Run the read-only guard on the configured body.

firstFunctionCallSeen = true
// A text part, even an empty signature carrier, ends the parallel group:
// the Gemini API rejects an unsigned functionCall after it. Thought parts do not.
if hasFunctionCall || !part.Get("thought").Bool() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Reset Gemini function-call groups across server-tool parts

The new group-reset condition is bypassed by the earlier toolCall/toolResponse early return. For a model turn containing a signed functionCall, then a server-tool part, then another unsigned functionCall, the sanitizer leaves the second call unsigned, while the updated validator treats the non-thought server-tool part as a separator and requires a signature. The sanitized payload therefore remains invalid upstream.

diagnosticsState = claudeDiagnosticsRequestState{}
}
extraBetas, bodyForUpstream = extractAndRemoveBetas(bodyForUpstream)
bodyForUpstream = stripPromptCacheOptions(bodyForUpstream)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Do not delete prompt-cache configuration after payload rules

This cleanup runs after ApplyPayloadConfigWithTrackedPaths, bypassing the required final semantic barrier. A payload rule setting prompt_cache_options.mode=explicit is applied too late to suppress the automatic cache breakpoints and is then deleted here, so the outgoing request uses automatic rather than explicitly configured placement. The streaming and token-count paths contain the same post-rule deletion.

@ericlitman

Copy link
Copy Markdown
Contributor Author

Closing: the review scope changed. Eric (2026-10-27): "i don't want to fix anything that's an issue in upstream - our only goal here is to validate our changes". Upstream code is out of scope. Our fork's own changes on v8.0.44 get one review PR instead. Findings from this part are recorded in the HOUSE-79 adjudication record. None was a defect in our code.

@ericlitman ericlitman closed this Oct 27, 2026
@ericlitman
ericlitman deleted the review/house79/chunk-04 branch October 27, 2026 19:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant