Bug Description
Setting reasoning.enabled = false and effort = "none" in .forge.toml does not prevent the effort parameter from being sent to the Anthropic API. Models that do not support output_config.effort (e.g. Claude Haiku 4.5) reject the request with a 400.
Reported in #2924 by @pcastellazzi.
Root Cause
Two interacting bugs:
-
Config merge direction (crates/forge_app/src/agent.rs:164-167): The merge uses overwrite_none with the agent as base, so the forge agent's reasoning.enabled: true (hardcoded in forge.md:5-6) silently overrides the user's enabled: false. The user config can never turn off reasoning through .forge.toml.
-
Effort::None semantics (crates/forge_app/src/dto/anthropic/request.rs:153): Effort::None is documented as "No reasoning; skips the thinking step entirely" but the Anthropic DTO maps it to OutputEffort::Low, which enables low-effort reasoning. The OpenAI path already handles this correctly (maps to "none").
Fix
- Reverse the merge direction so user config takes priority (matching the compact config pattern already used in the same file).
- When
Effort::None reaches the Anthropic DTO, return (None, None) instead of OutputEffort::Low.
Bug Description
Setting
reasoning.enabled = falseandeffort = "none"in.forge.tomldoes not prevent the effort parameter from being sent to the Anthropic API. Models that do not supportoutput_config.effort(e.g. Claude Haiku 4.5) reject the request with a 400.Reported in #2924 by @pcastellazzi.
Root Cause
Two interacting bugs:
Config merge direction (
crates/forge_app/src/agent.rs:164-167): The merge usesoverwrite_nonewith the agent as base, so the forge agent'sreasoning.enabled: true(hardcoded inforge.md:5-6) silently overrides the user'senabled: false. The user config can never turn off reasoning through.forge.toml.Effort::None semantics (
crates/forge_app/src/dto/anthropic/request.rs:153):Effort::Noneis documented as "No reasoning; skips the thinking step entirely" but the Anthropic DTO maps it toOutputEffort::Low, which enables low-effort reasoning. The OpenAI path already handles this correctly (maps to"none").Fix
Effort::Nonereaches the Anthropic DTO, return(None, None)instead ofOutputEffort::Low.