Skip to content

Fix empty transcript on resume; add shift+↑/↓ prompt jump - #6

Merged
tdwd merged 1 commit into
mainfrom
fix/resume-history-and-prompt-jump
Aug 7, 2026
Merged

Fix empty transcript on resume; add shift+↑/↓ prompt jump#6
tdwd merged 1 commit into
mainfrom
fix/resume-history-and-prompt-jump

Conversation

@tdwd

@tdwd tdwd commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

The bug

Resuming a session showed no chat history for any project whose path contains a space, dot, or underscore.

projectSlug replaced only / with -. Claude replaces every non-alphanumeric character — one dash each, no run-collapsing:

cwd claude writes cathode looked for
/Users/w/Work/Triple Down/web -Users-w-Work-Triple-Down-web -Users-w-Work-Triple Down-web
/Users/w/.config/wezterm -Users-w--config-wezterm -Users-w-.config-wezterm
/Users/w/Work/x/fuel_forecaster ...-fuel-forecaster ...-fuel_forecaster

The lookup landed on a path that doesn't exist, and both readers degrade silently to empty: the ctrl+r picker lost claude's own sessions (leaving only cathode's own store — which is why resume still worked, just with a blank transcript), and the replay rendered nothing. Projects without special characters, including this repo, were unaffected — hence the intermittency. Nine of 47 local project dirs were hit, including every .claude-worktrees worktree via the leading dot.

Second cause, same symptom

claudeRecord typed message.content as an array, but claude also writes it as a bare string for a plain typed prompt. That decode fails, and loadPriorTranscript skipped the whole record on any error — dropping the user's own prompts out of a replay. claude_sessions.go already handled both encodings in its own decoder; the two had diverged. They now share contentBlocks.

Changes

  • projectdir.go (new) — the corrected slug rule plus claudeProjectDir, now the single resolution point for the picker and the replay. When the slug misses, it falls back to matching the cwd stamped inside the session records — ground truth, so resume survives future drift in claude's naming instead of failing silently. The scan stays off the common path.
  • transcript.go — routes through claudeProjectDir; contentBlocks decodes both content encodings.
  • claude_sessions.go — drops its duplicate decoder and the moved helper.
  • CLAUDE.md — documents the rule and why getting it wrong is silent.

Also lands jump.go: shift+↑/↓ steps the viewport through past prompts, deriving the anchor from vp.YOffset (via m.entryLine) so it composes with wheel, PageUp and auto-follow rather than holding a cursor to invalidate.

Testing

go build, go vet, go test ./... all pass. New tests pin the slug rule against real observed directories, the recorded-cwd fallback, slug-wins-over-scan, both content encodings, and an end-to-end replay from a spaced cwd.

Verified live against the previously-broken session: the picker now finds it (opus-5, first prompt intact) and the replay returns 40 entries with the context gauge seeded at 820,451 tokens. Before the fix, both were empty.

Resuming a session showed no chat history for any project whose path
contains a space, dot, or underscore.

projectSlug replaced only "/" with "-", but claude replaces every
non-alphanumeric character, one dash each with no run-collapsing. So
`/Users/w/Work/Triple Down/web` slugs to `-Users-w-Work-Triple-Down-web`,
not `-Users-w-Work-Triple Down-web`. The lookup landed on a path that
does not exist and both readers degrade silently to empty: the ctrl+r
picker lost claude's own sessions (leaving only cathode's store, which is
why resume still worked) and the replay rendered nothing. Every
.claude-worktrees worktree was affected too, via the leading dot.

The slug rule and the directory resolution move to projectdir.go, shared
by the picker and the replay. When the slug misses, claudeProjectDir
falls back to matching the cwd stamped inside the session records —
ground truth, so resume survives future drift in claude's naming instead
of failing silently.

Second cause of the same symptom: claudeRecord typed message.content as
an array, but claude also writes it as a bare string for a typed prompt.
That decode fails, and loadPriorTranscript skipped the whole record on
any error, dropping the user's own prompts from a replay.
claude_sessions.go already handled both encodings in its own decoder; the
two had diverged. They now share contentBlocks.

Also lands jump.go: shift+↑/↓ steps the viewport through past prompts,
deriving the anchor from vp.YOffset (via m.entryLine) so it composes with
wheel, PageUp and auto-follow rather than holding a cursor to invalidate.
@tdwd
tdwd merged commit 9c19c58 into main Aug 7, 2026
1 check passed
@tdwd
tdwd deleted the fix/resume-history-and-prompt-jump branch August 7, 2026 14:33
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