Skip to content

Claude sessions don't always resume correctly on restore #75

Closed

Description

@mortenaslo

Bug

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.

Activity

  1. AThraen commented on Aug 7, 2026

    @AThraen
    Contributor

    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:

    1. Session A (folder F) restores → newest jsonl is X → launches claude --resume X.
    2. A's Claude process starts writing to X.jsonl → X is now the newest file in F.
    3. ~2s later session B (same folder F) restores → GetLastSessionId(F) → returns X again, because A just touched it.
    4. 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:

    1. Add ClaudeSessionId (string?) to ShellSession, persisted to state.json.
    2. On first launch of a Claude session, generate a UUID, pass --session-id <uuid>, and store it on the session.
    3. On restore, pass --resume <ClaudeSessionId> — no filesystem lookup at all.
    4. 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).

  2. AThraen commented on Aug 10, 2026

    @AThraen
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions