fix: reasoning models reject tool_choice=required; bump to 1.0.2 (closes #503, #505)#508
Conversation
Every reasoning-capable provider (Anthropic with thinking, DeepSeek V4 thinking, etc.) rejects the combination of ``tool_choice="required"`` and reasoning/thinking enabled. Strix sets both unconditionally, so scans fail immediately with provider-side 400s: Anthropic: "Thinking may not be enabled when tool_choice forces tool use" DeepSeek: "Thinking mode does not support this tool_choice" This is a provider contract — Anthropic's extended-thinking docs state the rule explicitly, and LiteLLM has declined to normalize it (BerriAI/litellm#8883). LangChain and pydantic-ai both shipped client-side normalization for the same constraint. Fixes: 1. make_model_settings(): when reasoning is enabled, leave tool_choice unset (model self-selects). When reasoning is off, keep the existing "required" safety net. The lifecycle-tool requirement is still enforced by _finish_tool_use_behavior in the agent factory. 2. STRIX_REASONING_EFFORT="none" used to crash with AttributeError: 'NoneType' object has no attribute 'get' because the literal string "none" was truthy and made it into LiteLLM's reasoning path. Treat it as "do not attach Reasoning(...)" — restores the documented escape hatch. 3. system_prompt: reinforce that text-only turns terminate the scan, so the agent leans harder on the lifecycle tools now that the forced-tool-choice safety net is gone for reasoning runs. 4. runner: surface a clear error log when a non-interactive scan ends without finish_scan being called (text-ended turn), so users see what happened instead of a silent half-scan. Closes #503, #505 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Greptile SummaryThis PR fixes provider-side 400 errors that occurred when
Confidence Score: 5/5Safe to merge — the changes are well-scoped and directly fix documented provider incompatibilities without altering unrelated paths. The use_reasoning guard is logically correct for all values in the ReasoningEffort literal. The runner defensive check only fires in non-interactive mode and only logs — it does not alter control flow. No existing non-reasoning paths are changed. No files require special attention. Important Files Changed
Reviews (2): Last reviewed commit: "Also handle dict final_output in scan-co..." | Re-trigger Greptile |
Greptile review on PR #508 flagged that the post-run defensive check only treated string final_output. If the SDK ever returns a structured dict (depending on output_type configuration), the check would false-positive and log "scan ended without finish_scan" on every successful reasoning-model scan. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fixes #503 and #505.
Issue
Every scan against a reasoning-capable model fails immediately with provider-side 400s:
"Thinking may not be enabled when tool_choice forces tool use""Thinking mode does not support this tool_choice"Affects
anthropic/claude-sonnet-4-6,anthropic/claude-opus-4-7, DeepSeek V4 thinking, and any future reasoning-capable provider.Root cause
strix/core/inputs.py:make_model_settings()unconditionally setstool_choice="required"AND attachesReasoning(effort=...). This combination is rejected provider-side. Quoting Anthropic's docs:LiteLLM has declined to normalize this client-side (BerriAI/litellm#8883). LangChain and pydantic-ai both shipped client-side fixes for the same constraint (langchain#35544, pydantic-ai#3611).
Fixes
Behavior matrix after the fix
Provider impact
Version
Bumped to 1.0.2. After merge → tag `v1.0.2` → CI binaries + PyPI publish.
Docker image stays at `:1.0.0` (unaffected).