Skip to content

fix(core): drop tool parts when a failed turn loses its reasoning block - #40157

Open
asjad3 wants to merge 1 commit into
anomalyco:devfrom
asjad3:fix/errored-turn-orphaned-tool-use
Open

fix(core): drop tool parts when a failed turn loses its reasoning block#40157
asjad3 wants to merge 1 commit into
anomalyco:devfrom
asjad3:fix/errored-turn-orphaned-tool-use

Conversation

@asjad3

@asjad3 asjad3 commented Aug 2, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #38620

Type of change

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

What does this PR do?

Replaying an errored assistant turn can send Anthropic a tool_use with no thinking block
in front of it, which the API rejects with a 400.

The chain: reuseProviderMetadata is false once message.error is set, so the reasoning
part is built with providerMetadata: undefined. It then hits the empty-part filter just
below, which keeps a reasoning part only if it has text or provider state. Reasoning text
is empty whenever thinking display is "omitted" — the default on current Anthropic
models, where the signature lives in the metadata rather than the text. So the part has
neither, gets filtered out, and the turn's tool calls are left with nothing in front of
them.

This drops the tool parts too when that happens, so the replayed turn is at least
self-consistent. Both the tool calls and their trailing Message.tool results go, since
dropping only the calls would leave a tool_result with no matching tool_use — the same
error the other way round.

It only applies to same-model replay. A model switch lowers reasoning to plain text on
purpose, so no reasoning part survives there either, but those tool calls are still fine
and must be kept. Without that guard the model-switch test fails.

Note this is the second of the two options in #38620. #38763 already implements the first
(reuse the metadata on error) and predates this. I started there too, but it needs the
existing OpenAI expectation to change: itemId / reasoningEncryptedContent point at
provider-side state that a failed turn never committed, so replaying them isn't obviously
safe. Anthropic's signature is different — it signs content rather than pointing at server
state — but telling those apart means this layer knowing about specific providers. Both
options are defensible and it's your call; happy to close this if you prefer #38763.

How did you verify your code works?

Two tests in packages/core/test/session-runner-message.test.ts. The first covers the bug
and fails on dev (the turn replays a lone tool-call). The second covers a failed turn
whose reasoning still has text, so tool calls are kept — that one passes either way and is
there to stop the fix from dropping tool calls on every errored turn. I wrote it after the
first version of this change broke the model-switch test.

From packages/core:

  • bun test test/session-runner-message.test.ts — 8 pass
  • bun test test/session-*.test.ts — 203 pass, same as dev
  • bun typecheck — clean
  • prettier --check, oxlint on the changed files — clean, warning count unchanged

I could not reproduce the actual 400 end to end, since that needs a live Anthropic key and
an interrupt landing mid-step. The evidence here is the invariant at the unit level.

Screenshots / recordings

Not a UI change.

Checklist

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

AI assistance: written with Claude Code, reviewed by me, checks above run locally.

Replaying an errored assistant turn could send Anthropic a tool_use with no
preceding thinking block, which the API rejects with a 400.

reuseProviderMetadata is false once message.error is set, so the reasoning
part is emitted without its signature. Reasoning text is empty whenever
thinking display is "omitted" -- the default on current Anthropic models --
so the part then carries neither text nor provider state and is filtered out
as empty, leaving the turn's tool calls orphaned.

Drop the tool parts alongside the reasoning so the replayed turn stays
self-consistent. Scoped to same-model replay: a model switch lowers reasoning
to plain text on purpose, so its tool calls remain valid and are kept.

Fixes anomalyco#38620
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Potential duplicate found:

PR #38763 - fix(core): preserve reasoning metadata on errored assistant turns

Related PR:

PR #40148 - fix(opencode): drop truncated reasoning from replayed history

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

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

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.

V2: errored-message replay can 400 with Anthropic thinking + tool use

1 participant