Skip to content

fix(claude): projected sessions with thinking blocks are resumable again - #151

Merged
benbaarber merged 2 commits into
mainfrom
ben/fix-thinking-signature
Jul 30, 2026
Merged

fix(claude): projected sessions with thinking blocks are resumable again#151
benbaarber merged 2 commits into
mainfrom
ben/fix-thinking-signature

Conversation

@benbaarber

@benbaarber benbaarber commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Urgent extraction of the two resume-blocking fixes from #150 so they can land on main today, independent of the plugin stack.

The bug

Claude Code's JSONL carries an Anthropic-signed signature on every thinking block. Toolpath's derivation doesn't preserve signatures, and the projector re-emitted thinking blocks with "signature": null — which the API rejects outright:

API Error: 400 messages.1.content.0.thinking.signature.str: Input should be a valid string

An absent signature key is tolerated (the unsigned thinking block is dropped on replay, per docs/agents/formats/claude-code/writing-compatible-jsonl.md), so every session projected by p export claude or path resume that contained thinking blocks — most real agent sessions — was unresumable, failing on the first message after resume.

Fixes

  • toolpath-claude 0.12.2: #[serde(skip_serializing_if = "Option::is_none")] on ContentPart::Thinking.signatureNone serializes as an absent key. Real signatures pass through untouched. Unit test asserts both directions.
  • path-cli 0.16.1 (toolpath-cli shim bumped in lockstep): the Claude JSONL export ends with a trailing newline. Claude Code appends to the projected file on resume, and without one the first appended entry landed on the same line as the last projected entry, corrupting the JSONL. (The gemini/codex exports already did this.)

Verification

  • Live end-to-end: a projected thinking-bearing session that previously 400'd now resumes cleanly under claude -r and correctly summarizes its own history; on-disk checks confirm zero "signature":null occurrences and a trailing newline.
  • cargo test -p toolpath-claude -p path-cli green; scripts/quality_gates.sh 7/7 on this branch.

Not included (stays in #150): the p export claude overwrite guard + --force and the path resume already-local short-circuit — behavior changes, not API compatibility. #150 carries the same two hunks as this PR and will rebase cleanly or trivially after this merges.

Follow-up (not here): preserve real signatures through derivation via Turn.extra["claude"] for lossless thinking replay.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Two fixes found by live-resuming a projected session against the real API:

- toolpath-claude 0.12.2: ContentPart::Thinking serializes a None signature
  as an absent key instead of "signature": null. The Anthropic API rejects
  a null signature outright (400 thinking.signature.str: Input should be a
  valid string), so any session projected by p export claude / path resume
  that contained thinking blocks — most real agent sessions — failed on the
  first message after resume. An absent signature is tolerated: the unsigned
  thinking block is dropped on replay, per the documented writer contract in
  docs/agents/formats/claude-code/writing-compatible-jsonl.md. Real
  signatures still pass through untouched.

- path-cli: the Claude JSONL export ends with a trailing newline. Claude
  Code appends to the projected file on resume, and without one the first
  appended entry landed on the same line as the last projected entry,
  corrupting the JSONL (gemini/codex exports already did this).

Verified live: a projected thinking-bearing session that previously 400'd
now resumes cleanly under claude -r and summarizes its own history.

@akesling akesling 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.

LGTM

The Claude export newline fix rides a patch bump so the pending 0.16
release carries it distinctly. The toolpath-cli shim tracks path-cli in
lockstep as usual.
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown

🔍 Preview deployed: https://abfd7acd.toolpath.pages.dev

@benbaarber
benbaarber merged commit 1664a23 into main Jul 30, 2026
3 checks passed
@benbaarber
benbaarber deleted the ben/fix-thinking-signature branch July 30, 2026 20:23
ben-emp pushed a commit that referenced this pull request Jul 30, 2026
…th resume short-circuits

p export claude --project refuses to overwrite an existing session file (the
error names the id and suggests claude -r); a new --force flag restores the
old clobbering behavior. Found the hard way: same-machine round-trips —
share your own session, then resume it — silently replaced the richer local
original with the lossy projection. path resume short-circuits instead:
project_claude returns AlreadyLocal and the resume execs the local copy,
which may be newer than the shared document. Tests cover the diverged-local
untouched case and the refuse/--force pair.

(The signature-serialization and trailing-newline fixes discovered in the
same investigation landed on main separately via #151.)
benbaarber added a commit that referenced this pull request Jul 30, 2026
…th resume short-circuits

p export claude --project refuses to overwrite an existing session file (the
error names the id and suggests claude -r); a new --force flag restores the
old clobbering behavior. Found the hard way: same-machine round-trips —
share your own session, then resume it — silently replaced the richer local
original with the lossy projection. path resume short-circuits instead:
project_claude returns AlreadyLocal and the resume execs the local copy,
which may be newer than the shared document. Tests cover the diverged-local
untouched case and the refuse/--force pair.

(The signature-serialization and trailing-newline fixes discovered in the
same investigation landed on main separately via #151.)
ben-emp pushed a commit that referenced this pull request Aug 7, 2026
…th resume short-circuits

p export claude --project refuses to overwrite an existing session file (the
error names the id and suggests claude -r); a new --force flag restores the
old clobbering behavior. Found the hard way: same-machine round-trips —
share your own session, then resume it — silently replaced the richer local
original with the lossy projection. path resume short-circuits instead:
project_claude returns AlreadyLocal and the resume execs the local copy,
which may be newer than the shared document. Tests cover the diverged-local
untouched case and the refuse/--force pair.

(The signature-serialization and trailing-newline fixes discovered in the
same investigation landed on main separately via #151.)
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.

2 participants