Skip to content

fix(executors): don't inject thinking when tool_choice forces a tool (native Claude) - #4389

Merged
diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.31from
NomenAK:fix/claude-thinking-tool-choice-guard-2026-06-20
Jun 20, 2026
Merged

diegosouzapw merged 2 commits into
diegosouzapw:release/v3.8.31from
NomenAK:fix/claude-thinking-tool-choice-guard-2026-06-20

Conversation

@NomenAK

@NomenAK NomenAK commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

What

Don't inject thinking on native Claude OAuth when tool_choice forces a tool.

Why

The Claude Code wire-image emulation in open-sse/executors/base.ts injects thinking:{type:"adaptive"} for non-Haiku Claude models. But Anthropic rejects thinking (enabled/adaptive) when tool_choice forces a specific tool ({type:"any"|"tool"}):

400 "Thinking may not be enabled when tool_choice forces tool use."

So any Opus/Sonnet call that pins a tool — e.g. Claude Code's message_user, or agent harnesses that force a tool — gets a hard 400. There's already a disableThinkingIfToolChoiceForced helper (from the v3.8.3 wire-image work), but it runs earlier in the pipeline and is undone by this later injection.

How

  • Compute toolChoiceForced (tool_choice is "any" or {type:"any"|"tool"}) and derive effThinking = toolChoiceForced ? "off" : headerThinking.
  • Use effThinking in the three thinking branches, so forced tool_choice takes the existing off path (strips thinking + context_management) and skips the default adaptive injection. Thinking is preserved for normal calls; this also covers client-sent thinking + forced tool_choice; Haiku is unaffected.
  • Add tests/unit/claude-thinking-tool-choice-guard.test.ts (forced → no thinking; non-forced → adaptive thinking preserved), using the same executor harness as the existing #4307 test.

Notes

Observed live: Opus 4.8 / Sonnet 4.6 + forced tool_choice went 400 → 200 after this, while normal calls keep thinking:{type:adaptive}. Happy to revise if you'd prefer a different approach (e.g. folding this into disableThinkingIfToolChoiceForced at a single chokepoint).

@NomenAK
NomenAK requested a review from diegosouzapw as a code owner June 20, 2026 13:19

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request implements a guard to prevent Anthropic API errors by treating forced tool choices as an implicit 'thinking: off', and includes unit tests to verify this behavior. The review feedback is valid and important, noting that 'tb.output_config' must also be deleted when thinking is disabled to prevent 400 Bad Request errors from Anthropic.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread open-sse/executors/base.ts
@NomenAK

ghost commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review — good catch on output_config. Addressed in 5ad1551: the forced-off path now also deletes output_config (effort), since Anthropic rejects it without active thinking (covers a client-sent output_config or an x-omniroute-effort header landing alongside a forced tool_choice). Extended the unit test to assert it's stripped when forced and preserved otherwise.

Also corrected the base.ts file-size baseline so Fast Quality Gates passes.

NomenAK and others added 2 commits June 20, 2026 11:01
…(native Claude)

The Claude Code wire-image emulation in base.ts injects thinking:{type:"adaptive"}
for non-Haiku Claude models. Anthropic rejects thinking (enabled/adaptive) when
tool_choice forces a specific tool ({type:"any"|"tool"}) with a hard 400
"Thinking may not be enabled when tool_choice forces tool use". So Opus/Sonnet
calls that pin a tool (e.g. Claude Code's message_user) failed.

Treat forced tool_choice as an implicit thinking: off (effThinking) so thinking
is stripped only when forced and the adaptive injection is preserved otherwise.
Also covers client-sent thinking + forced tool_choice. Haiku unaffected.

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
…hinking guard (diegosouzapw#4389)

Co-authored-by: diegosouzapw <diegosouza.pw@gmail.com>
@diegosouzapw
diegosouzapw changed the base branch from release/v3.8.30 to release/v3.8.31 June 20, 2026 14:03
@diegosouzapw
diegosouzapw force-pushed the fix/claude-thinking-tool-choice-guard-2026-06-20 branch from 5ad1551 to 05b095b Compare June 20, 2026 14:03
@diegosouzapw
diegosouzapw merged commit ce8ff4a into diegosouzapw:release/v3.8.31 Jun 20, 2026
@diegosouzapw

ghost commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Merged into release/v3.8.31 🎉 Thanks @NomenAK — clean fix for the Anthropic "Thinking may not be enabled when tool_choice forces tool use" 400. Rebased onto the current release tip; the claude-thinking-tool-choice-guard suite is green (2/2, forced→stripped / non-forced→adaptive preserved) and the base.ts file-size baseline was reconciled. Ships in the next release.

@diegosouzapw diegosouzapw mentioned this pull request Jun 20, 2026
@diegosouzapw diegosouzapw mentioned this pull request Jun 27, 2026
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.

2 participants