Fix empty transcript on resume; add shift+↑/↓ prompt jump - #6
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Resuming a session showed no chat history for any project whose path contains a space, dot, or underscore.
projectSlugreplaced only/with-. Claude replaces every non-alphanumeric character — one dash each, no run-collapsing:/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-worktreesworktree via the leading dot.Second cause, same symptom
claudeRecordtypedmessage.contentas an array, but claude also writes it as a bare string for a plain typed prompt. That decode fails, andloadPriorTranscriptskipped the whole record on any error — dropping the user's own prompts out of a replay.claude_sessions.goalready handled both encodings in its own decoder; the two had diverged. They now sharecontentBlocks.Changes
projectdir.go(new) — the corrected slug rule plusclaudeProjectDir, now the single resolution point for the picker and the replay. When the slug misses, it falls back to matching thecwdstamped 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 throughclaudeProjectDir;contentBlocksdecodes 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 fromvp.YOffset(viam.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.