Skip to content

Rebuild provider-executed Anthropic blocks for a restored session - #162

Merged
shibayan merged 6 commits into
masterfrom
rebuild-provider-executed-blocks
Sep 2, 2026
Merged

Rebuild provider-executed Anthropic blocks for a restored session#162
shibayan merged 6 commits into
masterfrom
rebuild-provider-executed-blocks

Conversation

@shibayan

@shibayan shibayan commented Sep 1, 2026

Copy link
Copy Markdown
Member

What this changes

A session restored from persistence can replay a turn that used the code-execution beta: when rawRepresentation is gone — serialization deliberately strips it — the Anthropic conversion rebuilds server_tool_use and the code-execution result blocks from the typed contents, so calls stay paired with their results instead of dropping out and drawing a 400. Closes #126.

Parity

  • Reference checked: the block shapes follow the @anthropic-ai/sdk request param schemas (v0.122: BetaServerToolUseBlockParam, BetaCodeExecutionToolResultBlockParam, BetaBashCodeExecution…, BetaTextEditorCodeExecution…), and the receive-side mapping being inverted is this package's own (from-anthropic.ts), which itself matches Python's _parse_tool_result_from_mcp-era handling. Python does not rebuild these blocks — its transcripts hit the same loss — so the reconstruction rules are this repository's own, pinned by round-trip equivalence: for every fixture (code success and error, bash success and timeout, text-editor view/str_replace/create/error), receive → serialize → restore → send is asserted byte-identical to the in-memory raw replay.
  • Wire format affected: yes
  • Public API affected: no
  • Breaking change: no

The server_tool_use family is inferred from the kind of content answering the call, since the typed code_interpreter_tool_call folds all three code-execution tools into one kind; bash files are gathered from the hosted_file siblings the receive side places before the shell result; return_code and file_type are not in the typed model and default to 0 / 'text'. Error outputs parsed from code-execution and text-editor failures now also carry the wire's code under ErrorContent.errorCode (additive; both layers proven individually necessary by negative control). A transcript still holding rawRepresentation replays the exact bytes — a raw block with an unmodelled extra field is pinned to survive — and rawRepresentation is still never serialized. A function_result answering a local call keeps the local tool_result mapping after a restore. Restoring web_search/web_fetch turns stays out of scope, per the issue.

Checklist

  • pnpm check passes (lint, typecheck, build, test)
  • Behaviour changes are covered by a test that fails without the change
  • Public API changes are reflected in the package README and CHANGELOG.md

🤖 Generated with Claude Code

Content describing a call the provider ran itself - code execution,
bash, the text editor - was replayed only from rawRepresentation, which
serialization deliberately strips. A transcript restored from
persistence therefore dropped those blocks, and the text editor's
function_result went out as a tool_result answering a call the request
no longer contained, which the Messages API rejects.

The conversion now rebuilds the blocks from the typed contents when the
raw block is gone. The server_tool_use family is inferred from the kind
of content answering the call, since the typed call folds all three
code-execution tools into one kind; the result payloads follow the
request schemas, with the bash files gathered from the hosted_file
siblings the receive side places before the shell result. Error outputs
parsed from code-execution and text-editor failures now also carry the
wire's code under ErrorContent.errorCode, without which a restored
error turn cannot name the code the schema requires. For every fixture
whose typed form captures what the wire said, the rebuilt request is
asserted identical to the in-memory replay; a transcript still holding
rawRepresentation replays the exact bytes, and rawRepresentation is
still never serialized.

Closes #126

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 1, 2026 18:00
@shibayan shibayan added the bug Usage: [PRs], Target: bug fixes and regressions; issues use the Bug issue type label Sep 1, 2026
@github-actions github-actions Bot added documentation Usage: [Issues, PRs], Target: documentation changes anthropic Usage: [Issues, PRs], Target: packages/anthropic labels Sep 1, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes Anthropic “provider-executed” (code execution / bash / text editor) turn replay after restoring a persisted session by rebuilding the missing Messages API blocks when rawRepresentation has been stripped during serialization.

Changes:

  • Reconstruct server_tool_use + provider tool-result blocks from typed Content when rawRepresentation is absent (restored sessions).
  • Preserve error codes through persistence by additionally storing Anthropic tool error codes in ErrorContent.errorCode.
  • Add replay/round-trip tests to assert restored replay matches in-memory replay for provider-executed turns, and document the fix in the changelog.
File summaries
File Description
packages/anthropic/src/to-anthropic.ts Adds restored-session reconstruction logic for provider-executed tool call/result blocks.
packages/anthropic/src/from-anthropic.ts Captures tool error codes into ErrorContent.errorCode to enable faithful rebuild after persistence.
packages/anthropic/src/code-execution.test.ts Updates expectations for new errorCode field on error outputs.
packages/anthropic/src/code-execution-replay.test.ts New fixtures asserting restored replay is byte-identical to in-memory replay for provider-executed turns.
CHANGELOG.md Documents restored-session replay behavior fix for the Anthropic package.
Review details

Suppressed comments (2)

packages/anthropic/src/to-anthropic.ts:407

  • These restored-session cases should not emit blocks with missing/empty ids (callId is optional on hosted tool contents). As written, callId ?? '' can produce id: '' / tool_use_id: '', which is not pairable and can cause Messages API rejection.
    case 'code_interpreter_tool_call': {
      // Reached only without a raw block — a restored transcript — since the raw path above
      // replays the exact bytes otherwise. The same holds for the result cases below.
      blocks.push(rebuiltServerToolUse(content, ctx.families.get(content.callId ?? '') ?? 'code_execution'));
      return;

packages/anthropic/src/to-anthropic.ts:421

  • For bash replays, malformed file entries are preserved by the receive side as unknown content items (unknownType bash_code_execution_output) placed immediately before the shell_tool_result. The rebuild currently only gathers hosted_file siblings, so restored replays drop those entries from the rebuilt payload and may incorrectly emit them as standalone unknown blocks.
    case 'shell_tool_result': {
      // The files a bash run produced sit as `hosted_file` siblings *before* the shell result —
      // that is where the receive side put them — so the rebuilt payload gathers the contiguous
      // run immediately preceding this content.
      const files: Content[] = [];
  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/anthropic/src/to-anthropic.ts
Comment thread packages/anthropic/src/to-anthropic.ts
Comment thread packages/anthropic/src/to-anthropic.ts
shibayan and others added 2 commits September 2, 2026 09:59
Review caught three fidelity gaps in the restored-session rebuild. A
string server_tool_use input - which the receive side records as
itself - was collapsed to {}; the rebuild now parses the recorded text
when it can and sends it back as the string it was otherwise, which is
type-valid because the request schema types input as unknown. A
malformed file entry the receive side preserves as unknown content was
dropped from the rebuilt result payloads; it now goes back in place for
both the code and bash lists. And the bash siblings walk now carries
those preserved entries too, while the unknown case skips a file entry
that folds into the shell result that follows it - previously it leaked
as a standalone top-level block on both the raw and the rebuilt path.
Each case is pinned by a new round-trip equivalence fixture.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 2, 2026 01:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The restored-session reconstruction path can mis-rebuild code_execution_tool_result_error into a success-shaped code_execution_result when ErrorContent.errorCode is present but not in the known enum, which changes the payload variant/semantics instead of degrading to a valid error payload.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread packages/anthropic/src/to-anthropic.ts
Copilot AI review requested due to automatic review settings September 2, 2026 01:06
A stored errorCode marks the error-payload variant unambiguously, but
the rebuild only produced an error when the code was one the schema
enum knows - an unrecognized or future code fell through to a
success-shaped code_execution_result with return_code 0, inverting what
the turn reported. It now degrades to the unavailable code the same way
the text-editor path does, while a legacy prose-only lone error still
reads back as the stderr-only success it was.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

codeExecutionFamilies currently correlates missing callId values via '', which can mis-infer tool families for keyless hosted-tool contents and produce mismatched rebuilt server_tool_use.name in degenerate transcripts.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread packages/anthropic/src/to-anthropic.ts
Copilot AI review requested due to automatic review settings September 2, 2026 01:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The rebuild behavior is well-scoped, preserves raw replay when present, and is backed by comprehensive round-trip equivalence tests covering key provider-executed variants and edge cases.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

An empty callId is not an identity two contents can share - the rule
the unanswered-call filter already applies - but the family inference
keyed missing ids as '', letting a keyless shell result flip a
different keyless call's rebuilt server_tool_use name. Families are now
tracked only for non-empty ids, and a keyless call keeps the
code_execution default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 2, 2026 01:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The reconstruction behavior is well-scoped, preserves exact raw replay when available, and is backed by byte-equivalence replay tests covering the restored-session path.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@shibayan
shibayan merged commit 2cbcddf into master Sep 2, 2026
9 checks passed
@shibayan
shibayan deleted the rebuild-provider-executed-blocks branch September 2, 2026 01:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

anthropic Usage: [Issues, PRs], Target: packages/anthropic bug Usage: [PRs], Target: bug fixes and regressions; issues use the Bug issue type documentation Usage: [Issues, PRs], Target: documentation changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A restored session cannot replay an Anthropic provider-executed turn

2 participants