effort "off" does not disable thinking on Sonnet 5 / Opus 5 / Fable (DisableWire::None omits the parameter, which no longer means off)
- Dominant language
- Rust
- Stars
- 348
- Forks
- 36
- Avg merge
- 4h 44m
- Merged PRs (30d)
- 30
Description
## Claim
From the 0.24.1 changelog, introducing `/effort`:
> `default` clears the override; **`off` is a real level that disables reasoning**.
On current Anthropic models it does not. `effort: "off"` (and `/effort off`) leaves thinking fully enabled on `claude-sonnet-5`, `claude-opus-5` and `claude-fable-5`, so a user who explicitly opts out still pays the latency and the reasoning tokens — silently, on the one setting whose whole purpose is opting out.
## Cause
`adapter.rs` gives each provider a `DisableWire` describing how thinking is turned off. Anthropic's is `None` — "omit the parameter":
```rust
("anthropic", EffortWire::AnthropicBudget, DisableWire::None),
```
Omitting `thinking` was a correct disable for Claude 4.6 and earlier. It stopped being one on the current generation:
| Model | `thinking` omitted |
|---|---|
| Sonnet 4.6 / Opus 4.6 | no thinking — omit still works |
| Sonnet 5 | **thinks** (adaptive by default) |
| Opus 5 | **thinks** (adaptive by default) |
| Fable 5 | **always thinks**, cannot be disabled |
So the wire shape didn't change; what omitting it means did.
## The payload already exists in the codebase
Anthropic is one of only three providers left on `DisableWire::None`; five others carry real disable wires (`ThinkingToggle`, `ChatTemplateKwargs`, `GeminiZeroBudget`, `OllamaThink`).
Notably **GLM** — which mimics Anthropic's API — is on `ThinkingToggle`, which emits:
```json
{"thinking": {"type": "disabled"}}
```
That is Anthropic's own documented shape. dirge already sends exactly the payload Anthropic needs; it just doesn't send it to Anthropic.
## Why the fix is not a one-line table change
Model support is uneven, so a blanket switch to `ThinkingToggle` would trade this bug for 400s:
- **Sonnet 5** accepts `{"type": "disabled"}`.
- **Opus 5** accepts it **only at effort `high` or below** — pairing it with `xhigh`/`max` is rejected. Since `off` means no thinking at all, that combination shouldn't arise, but the constraint is worth encoding rather than assuming.
- **Fable 5 rejects it outright** — thinking is unconditional there. `off` can never be honoured; warning that the level is unavailable seems more honest than silently accepting it.
- **Claude 4.6 and earlier** must keep the current omit-the-parameter behaviour.
So it wants to be per-model, not per-provider — which may mean `DisableWire` needs a model-aware variant, or the choice moves to where the model id is known.
## Impact
This is what forces a non-thinking tier to be Haiku 4.5. There is currently no way to get a fast, non-thinking turn out of Opus 5 or Sonnet 5 through dirge, even though the API supports it.
Happy to put up a PR if the shape above looks right — say if you'd rather have it structured differently (e.g. keep `DisableWire` per-provider and gate on the model at the call site).
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.