Skip to content

Fix chat history loss on resume for symlinked workspaces - #115

Merged
pufit merged 1 commit into
ClickHouse:mainfrom
alex-fedotyev:alex/fix-resume-jsonl-realpath
Jun 18, 2026
Merged

Fix chat history loss on resume for symlinked workspaces#115
pufit merged 1 commit into
ClickHouse:mainfrom
alex-fedotyev:alex/fix-resume-jsonl-realpath

Conversation

@alex-fedotyev

Copy link
Copy Markdown
Contributor

Summary

The SDK resume guard _sdk_resume_file_exists built the Claude Code history path from the unresolved workspace path, but the CLI encodes the realpath of the cwd. When the workspace is a symlink (the Docker deployment points /root/nerve-workspace at /Users/.../nerve-workspace), the guard looked under ~/.claude/projects/-root-nerve-workspace/, which never exists. It concluded the transcript was missing, cleared the stored sdk_session_id, and the next turn started a fresh conversation with no history.

Because the guard only runs when the in-memory client is gone, this showed up after a daemon restart, after the 60-minute idle-client sweep, or after a CLI crash. It was worst with parallel sessions: inactive ones get their clients reaped, so returning to them wiped their history while the actively-typed session looked fine. The guard was added in #65, so this is a regression for symlinked deployments (before it, the stale id went straight to --resume and the CLI resolved the symlink itself).

The fix resolves the workspace realpath before encoding, matching the CLI's own derivation, and falls back to the unresolved path for non-symlinked layouts. The fail-open on unexpected stat errors is kept. The houseofagents session tailer (runner.py) had the same bug (UI-only, degrades gracefully), fixed the same way.

Note: already-cleared sdk_session_id rows are not auto-recovered by this change. The .jsonl files still exist on disk, but relinking them is out of scope here.

Test plan

  • pytest tests/test_engine.py -v (37 passed), including three new cases: symlinked-workspace positive, genuinely-missing negative, and the unresolved-path fallback.
  • pytest tests/test_sessions.py (45 passed).
  • Verified against live on-disk state: for a real session id, the old guard returns False (the bug) and the new guard returns True; a bogus id still returns False.
  • No frontend change, so no npm run build.

The SDK resume guard derived the Claude Code history path from the
unresolved workspace path, but the CLI encodes the realpath of the
cwd. When the workspace is a symlink (the Docker deployment points
/root/nerve-workspace at /Users/.../nerve-workspace), the guard
checked a directory that never exists, concluded the transcript was
missing, and cleared the stored sdk_session_id. The next turn then
started a fresh conversation with no history. This surfaced after a
daemon restart or the idle-client sweep, and most visibly across
parallel sessions whose clients had been reaped.
Resolve the workspace realpath before encoding, matching the CLI's
own derivation, and fall back to the unresolved path for
non-symlinked layouts. The broad fail-open on stat errors is kept so
an unexpected error still attempts the resume. Apply the same
realpath resolution to the houseofagents session tailer, which had
the identical bug (UI-only, degrades gracefully today).
Add unit tests for the symlinked-workspace positive case, the
genuinely-missing negative case, and the unresolved-path fallback.
@pufit
pufit merged commit 97d7cd7 into ClickHouse:mainJun 18, 2026
Sign up for freeto 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

@alex-fedotyev@pufit