Skip to content

fix(resume): a replayed transcript had lost every tool result - #27

Merged
ericleepi314 merged 1 commit into
mainfrom
replay-tool-results
Aug 19, 2026
Merged

fix(resume): a replayed transcript had lost every tool result#27
ericleepi314 merged 1 commit into
mainfrom
replay-tool-results

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

What

Resuming a session redrew its tool calls with nothing under them. Captured by running a session, killing it, and replaying it with /sessions <id>:

before                                  after
⏺ Read(src/domain/usage.ts)             ⏺ Read(src/domain/usage.ts)
                                          ⎿  Read 3 lines
⏺ Bash(ls -1 src | head -12)            ⏺ Bash(ls -1 src | head -12)
                                          ⎿  __tests__
                                             app
                                             App.tsx
                                             …

Every result the session had ever produced was gone.

Why

rehydrate folded tool/call into a row and never looked at tool/result at all, and the transcript builder discarded the row's text on purpose — reasonably, back when that text was the model-facing payload nobody wants pasted into a transcript.

It no longer is. The card projections (#15, #17) now produce a real summary, and the harness persists each card's structured shape in the session log precisely so presentResult reproduces the identical card from a stored event:

the read tool projects them through its output.presentationMeta (persisted with the session log) and presentResult narrows that metadata back into this view on live and replay paths alike

So replay presents its results the same way the live path does, and the row's text is that summary.

Details

  • presentResult takes the call's name and arguments as parameters instead of reading them off the live maps — that is what lets replay share it.
  • Results are matched to their call by id, not by arrival order, because parallel calls interleave their results in the log.
  • A write or edit cannot draw its diff card on the flat replay trail, so it gets the card's own header as text — Wrote 3 lines to notes.txt, Added 1 line, removed 1 line — rather than the "file has been updated successfully" boilerplate addressed to the model.

Test

  • npm run typecheck, npm test — 150 files / 1953 passing
  • node scripts/verify-boundary.mjs
  • harnessClient.test.ts: the stored log now carries a tool/result, and the rehydrated row asserts its presented text
  • messages.test.ts: the tool row's text reaches the trail line (the fixture's 'ignored raw result' was the old contract, now 'Found 3 files')
  • live: a two-pass probe — run tools, kill, /sessions <id> — showing the replayed trail matching the live one

🤖 Generated with Claude Code

Resuming a session redrew its tool calls with nothing under them:

    ⏺ Read(src/domain/usage.ts)
    ⏺ Bash(ls -1 src | head -12)

Every result the session had ever produced was gone. `rehydrate` folded
`tool/call` into a row and never looked at `tool/result` at all, and the
transcript builder discarded the row's text on purpose — reasonably, back
when that text was the model-facing payload nobody wants pasted into a
transcript.

It no longer is. The card projections now carry a real `⎿` summary, and
the harness persists each card's structured shape in the session log
precisely so `presentResult` reproduces the identical card from a stored
event. So replay presents its results the same way the live path does, and
the row's text IS that summary:

    ⏺ Read(src/domain/usage.ts)
      ⎿  Read 3 lines
    ⏺ Bash(ls -1 src | head -12)
      ⎿  __tests__
         app
         …

`presentResult` takes the call's name and arguments as parameters instead
of reading them off the live maps, which is what lets replay share it.
Results are matched to their call by id, not by arrival order, because
parallel calls interleave their results in the log.

A write or edit cannot draw its diff card on the flat replay trail, so it
gets the card's own header as text — `Wrote 3 lines to notes.txt`, `Added
1 line, removed 1 line` — rather than the "file has been updated
successfully" boilerplate addressed to the model.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ericleepi314
ericleepi314 merged commit 8823a7b into main Aug 19, 2026
1 check passed
@ericleepi314
ericleepi314 deleted the replay-tool-results branch August 19, 2026 15:07
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.

1 participant