Uh oh!
There was an error while loading. Please reload this page.
Fix chat history loss on resume for symlinked workspaces - #115
Merged
pufit merged 1 commit intoJun 18, 2026
Conversation
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.
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 freeto 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.
Summary
The SDK resume guard
_sdk_resume_file_existsbuilt 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-workspaceat/Users/.../nerve-workspace), the guard looked under~/.claude/projects/-root-nerve-workspace/, which never exists. It concluded the transcript was missing, cleared the storedsdk_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
--resumeand 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_idrows are not auto-recovered by this change. The.jsonlfiles 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).npm run build.