You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Claude sessions don't always resume correctly on restore #75
When AutoResumeClaude is on and the app restores sessions on startup, the Claude conversation is not always picked up correctly — some sessions come back as a fresh claude invocation instead of resuming the prior conversation.
Where it happens
The resume path looks at ClaudeSessionService.GetLastSessionId(session.WorkingFolder) and appends --resume <id> to the launch command. Suspected failure modes worth checking:
Multiple Claude sessions in the same WorkingFolder — they all resolve to the same "last session id" and so they can't both resume their respective conversations.
The Claude project files under ~/.claude/projects/ may not yet have a session id for a brand-new conversation that was open at app close.
Sleep/wake vs. close/reopen vs. app-restart may take different paths.
Expected
Each restored Claude session resumes the conversation it had open at app close, deterministically.
Notes
Probably needs Claude session id to be tracked per ShellSession (persisted to state.json) at the time the session is launched / observed, rather than re-derived from the folder at restore time.
Root cause confirmed by code trace — and the race is self-poisoning
ShellSession has no field for a Claude session id (Models/ShellSession.cs:11-58 — Id is CodeShellManager's own GUID). The resume id is never persisted; it is re-derived from the filesystem at restore time:
MainWindow.xaml.cs:1059 → ClaudeSessionService.GetLastSessionId(session.WorkingFolder) → returns whichever *.jsonl in ~/.claude/projects/<folder-as-dashes>/ has the newest LastWriteTimeUtc (ClaudeSessionService.cs:38-45).
The restore loop (MainWindow.xaml.cs:284-301) is sequential and awaited, with ClaudeLaunchStaggerMs (default 2000ms) between consecutive Claude launches. That combination doesn't just return the same id for same-folder sessions — it actively corrupts the answer as it goes:
Session A (folder F) restores → newest jsonl is X → launches claude --resume X.
A's Claude process starts writing to X.jsonl → X is now the newest file in F.
~2s later session B (same folder F) restores → GetLastSessionId(F) → returns X again, because A just touched it.
Both tabs resume conversation X. B's real conversation Y is orphaned, and two claude.exe processes are attached to the same .jsonl.
This explains the "not always": it's a race between A's first flush and B's lookup. Single-session-per-folder users never see it. Note the 2s stagger makes it more reliably wrong, since 2s is ample for A to flush — so the mitigation for ~/.claude.json corruption worsens this bug.
The CLI supports owning the id, which is the real fix
claude --help confirms:
--session-id <uuid> Use a specific session ID for the conversation
-r, --resume [value] Resume a conversation by session ID, or ...
--fork-session When resuming, create a new session ID
So we don't have to guess. Proposed fix:
Add ClaudeSessionId (string?) to ShellSession, persisted to state.json.
On first launch of a Claude session, generate a UUID, pass --session-id <uuid>, and store it on the session.
On restore, pass --resume <ClaudeSessionId> — no filesystem lookup at all.
Keep GetLastSessionId only as a migration fallback for sessions already in state.json with no stored id, and note it's unreliable for multi-session folders.
This makes resume deterministic, removes the race entirely, and fixes the N-sessions-one-folder case that no filesystem heuristic can resolve. --fork-session is also worth exposing later for "resume this conversation as a new branch".
Care needed: --session-id on a reused id should be verified not to error if the conversation already exists, and the flag must not be injected when the user's own Args already specify --session-id, --resume, or --continue (the existing guard at MainWindow.xaml.cs:1056-1057 covers the latter two).
A second, distinct root cause found — fixed in #85
The No conversation found with session ID: <uuid> failures reported today are not the race described above. Separate bug, same code path:
ClaudeSessionService hardcoded ~/.claude as Claude's data directory, but Claude Code honors CLAUDE_CONFIG_DIR. When that's set, conversations live under $CLAUDE_CONFIG_DIR/projects/, and any stale ~/.claude/projects/ tree left over from before the env var was set still contains old .jsonl files. So GetLastSessionId returned a valid-looking UUID from the wrong store, and --resume failed outright rather than resuming the wrong conversation.
On the reporting machine the stale store resolved to a session last written two months prior — exactly the id in the error — while the live store had a conversation modified that same day.
Fixed in #85. This issue stays open — the same-folder race is untouched by that PR, and its durable fix is still the one described above: persist a ClaudeSessionId per ShellSession and launch with --session-id <uuid> so we own the id instead of inferring it from newest-file-wins. Worth noting the two bugs compound: reading the right directory doesn't help if two tabs in one folder still resolve to the same id.
Activity
AThraen commentedon Aug 7, 2026
Root cause confirmed by code trace — and the race is self-poisoning
ShellSessionhas no field for a Claude session id (Models/ShellSession.cs:11-58—Idis CodeShellManager's own GUID). The resume id is never persisted; it is re-derived from the filesystem at restore time:MainWindow.xaml.cs:1059→ClaudeSessionService.GetLastSessionId(session.WorkingFolder)→ returns whichever*.jsonlin~/.claude/projects/<folder-as-dashes>/has the newestLastWriteTimeUtc(ClaudeSessionService.cs:38-45).The restore loop (
MainWindow.xaml.cs:284-301) is sequential and awaited, withClaudeLaunchStaggerMs(default 2000ms) between consecutive Claude launches. That combination doesn't just return the same id for same-folder sessions — it actively corrupts the answer as it goes:X→ launchesclaude --resume X.X.jsonl→Xis now the newest file in F.GetLastSessionId(F)→ returnsXagain, because A just touched it.X. B's real conversationYis orphaned, and twoclaude.exeprocesses are attached to the same.jsonl.This explains the "not always": it's a race between A's first flush and B's lookup. Single-session-per-folder users never see it. Note the 2s stagger makes it more reliably wrong, since 2s is ample for A to flush — so the mitigation for
~/.claude.jsoncorruption worsens this bug.The CLI supports owning the id, which is the real fix
claude --helpconfirms:So we don't have to guess. Proposed fix:
ClaudeSessionId(string?) toShellSession, persisted tostate.json.--session-id <uuid>, and store it on the session.--resume <ClaudeSessionId>— no filesystem lookup at all.GetLastSessionIdonly as a migration fallback for sessions already instate.jsonwith no stored id, and note it's unreliable for multi-session folders.This makes resume deterministic, removes the race entirely, and fixes the N-sessions-one-folder case that no filesystem heuristic can resolve.
--fork-sessionis also worth exposing later for "resume this conversation as a new branch".Care needed:
--session-idon a reused id should be verified not to error if the conversation already exists, and the flag must not be injected when the user's ownArgsalready specify--session-id,--resume, or--continue(the existing guard atMainWindow.xaml.cs:1056-1057covers the latter two).AThraen commentedon Aug 10, 2026
A second, distinct root cause found — fixed in #85
The
No conversation found with session ID: <uuid>failures reported today are not the race described above. Separate bug, same code path:ClaudeSessionServicehardcoded~/.claudeas Claude's data directory, but Claude Code honorsCLAUDE_CONFIG_DIR. When that's set, conversations live under$CLAUDE_CONFIG_DIR/projects/, and any stale~/.claude/projects/tree left over from before the env var was set still contains old.jsonlfiles. SoGetLastSessionIdreturned a valid-looking UUID from the wrong store, and--resumefailed outright rather than resuming the wrong conversation.On the reporting machine the stale store resolved to a session last written two months prior — exactly the id in the error — while the live store had a conversation modified that same day.
Fixed in #85. This issue stays open — the same-folder race is untouched by that PR, and its durable fix is still the one described above: persist a
ClaudeSessionIdperShellSessionand launch with--session-id <uuid>so we own the id instead of inferring it from newest-file-wins. Worth noting the two bugs compound: reading the right directory doesn't help if two tabs in one folder still resolve to the same id.