Skip to content

fix(opencode): let config opt out of Anthropic thinking blockBinding - #46820

Merged
rekram1-node merged 3 commits into
anomalyco:devfrom
dkindlund:fix/block-binding-opt-out
Sep 2, 2026
Merged

fix(opencode): let config opt out of Anthropic thinking blockBinding#46820
rekram1-node merged 3 commits into
anomalyco:devfrom
dkindlund:fix/block-binding-opt-out

Conversation

@dkindlund

@dkindlund dkindlund commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Issue for this PR

Closes #46729

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

#46653 added blockBinding to every Claude request whose thinking type is adaptive or enabled. Its comment says "Models that do not run the check accept the field, so it is safe on every Claude", but that turns out not to be true — #46729 has reports of it being rejected on Bedrock, on Google Vertex, and on Anthropic-compatible endpoints:

thinking.adaptive.block_binding: Extra inputs are not permitted

I hit this on the third of those. To work out whether it was opencode or my endpoint, I sent requests with curl directly, no opencode involved. thinking: {"type":"adaptive"} is accepted on its own; adding block_binding to it gets rejected; adding the thinking-binding-controls-2026-08-01 beta header does not change that. But block_binding under thinking: {"type":"enabled", "budget_tokens": N} is accepted. So the field is only refused under the adaptive variant, which lines up with the error naming the path thinking.adaptive.block_binding.

Since the injection is unconditional there's no way for an affected user to avoid it short of pinning 1.18.25. That's worse on managed installs — where a root-owned config wins the merge, overriding options.thinking.type doesn't take, and registering a corrected provider doesn't either because enabled_providers can't be extended from user config. So there's no escape hatch at all.

The change makes anthropicBlockBinding respect an explicit blockBinding in config instead of always overwriting it:

  • blockBinding: false → omit the field
  • blockBinding: {...} → use the caller's value
  • absent → inject ANTHROPIC_BLOCK_BINDING, exactly as today

The default path is unchanged, so deployments where the field works keep getting it and the prefix-mismatch problem #46653 fixed stays fixed. I pulled the logic into a small applyBlockBinding helper so the @ai-sdk/anthropic, vertex, and bedrock reasoningConfig branches all behave the same way.

Affected users can then do:

"claude-opus-5": { "options": { "thinking": { "type": "adaptive", "blockBinding": false } } }

One thing I could not explain, in case it matters to you: claude-opus-4-8 is configured identically ({"thinking":{"type":"adaptive"}}) and by my reading of anthropicBlockBinding should also get the field — but the same endpoint that rejects claude-opus-5 accepts it. Either I've misread how anthropicThinksByDefault/anthropicOmitsThinking interact, or the API only validates this for Claude 5. I'd rather flag it than pretend I know.

If you'd prefer a different shape — a provider-level flag, a capability check, or retrying without the field on rejection — I'm happy to redo it.

How did you verify your code works?

Added 4 tests to packages/opencode/test/provider/transform.test.ts: opt-out on adaptive, opt-out on a think-by-default model, preserving an explicit value rather than overwriting it, and the bedrock reasoningConfig path.

Checked red/green rather than just green — with the test changes in place but transform.ts reverted to dev, those 4 fail; with the fix they pass:

without the source change:  440 pass, 4 fail
with the source change:     444 pass, 0 fail

All 440 pre-existing tests in that file still pass. Run with bun test test/provider/transform.test.ts from packages/opencode.

I also confirmed the underlying behaviour end to end outside the test suite: switching the model to thinking: {"type":"enabled", budgetTokens} unblocked claude-opus-5 and claude-sonnet-5 (default, max, and off variants) against the endpoint that was failing, which is the same code path this flag now lets you control.

Screenshots / recordings

N/A — not a UI change.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions github-actions Bot added needs:compliance This means the issue will auto-close after 2 hours. and removed needs:compliance This means the issue will auto-close after 2 hours. labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

dkindlund and others added 2 commits September 2, 2026 11:49
type is "adaptive" or "enabled", on the premise that "models that do not run the
check accept the field, so it is safe on every Claude".

That premise does not hold. Since 1.18.26 shipped, anomalyco#46729 collects the same
failure on three independent surfaces -- AWS Bedrock, Google Vertex, and
Anthropic-compatible proxy endpoints -- all returning:

  thinking.adaptive.block_binding: Extra inputs are not permitted

Probing an Anthropic-compatible endpoint directly with curl (no opencode in the
path) narrows it further:

  thinking {"type":"adaptive"}                                 -> OK
  thinking {"type":"adaptive"} + block_binding                 -> rejected
  thinking {"type":"adaptive"} + block_binding + beta header   -> rejected
  thinking {"type":"enabled",budget_tokens} + block_binding    -> OK

So the field is refused specifically under thinking.adaptive, and sending
thinking-binding-controls-2026-08-01 does not change that.

Because the injection is unconditional there is no way out: affected users must
pin 1.18.25. That is worse on managed installs, where a root-owned config wins
the merge, so overriding options.thinking.type or registering a corrected
provider both fail.

Honour an explicit blockBinding in config instead of always overwriting it:

  blockBinding: false        -> omit the field entirely
  blockBinding: {...}        -> use the caller's value
  absent (unchanged default) -> inject ANTHROPIC_BLOCK_BINDING as today

Default behaviour is untouched, so deployments that accept the field keep it.
Applies to @ai-sdk/anthropic, @ai-sdk/google-vertex/anthropic, and the bedrock
reasoningConfig path alike.

Tests: 4 added covering opt-out on adaptive, opt-out on a think-by-default model,
preserving an explicit value, and the bedrock path. Verified red/green --
440 pass / 4 fail without the source change, 444 pass / 0 fail with it.
@rekram1-node
rekram1-node force-pushed the fix/block-binding-opt-out branch from a7a53ce to 02d67ae Compare September 2, 2026 17:04
@rekram1-node
rekram1-node merged commit 68abdce into anomalyco:dev Sep 2, 2026
8 checks passed
filipeforattini added a commit to reddb-io/redcode that referenced this pull request Sep 5, 2026
…nomalyco#46820)

Ported from anomalyco/opencode 68abdce.

Co-authored-by: Aiden Cline <aidenpcline@gmail.com>

Upstream: 68abdce
filipeforattini added a commit to reddb-io/redcode that referenced this pull request Sep 5, 2026
…nomalyco#46820)

Ported from anomalyco/opencode 68abdce.

Co-authored-by: Aiden Cline <aidenpcline@gmail.com>

Upstream: 68abdce
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.

[BUG] thinking.adaptive.block_binding.prefix_mismatch_behavior: Extra inputs are not permitted

2 participants