feat: reconcile lost backend session ids from the tool's on-disk store - #100
Conversation
A crash that kills the tmux pane before the backend id is captured leaves
an offline session unresumable ("without an exact resumable backend
session id"). Recover it from the tool's own session store at resume time
instead of stranding the agent:
- backend-session-discovery: discoverBackendSessionId(tool, cwd) finds the
most recently active claude transcript under ~/.claude/projects for the
session's exact worktree (cwd-scoped so it cannot bind an unrelated
agent; uuid-validated). Codex carries its id in launch args, so only
claude needs disk recovery.
- resumeOfflineSession: when the durable backend id is missing and the
session is not being relaunched fresh, discover it from disk, use it for
an exact resume, and persist it via the new running session's saveState.
Falls back to the existing refuse-to-guess behavior if nothing is found.
This closes the residual memory<->disk divergence the W0 capture fix could
not reach (ids lost before this change, or while the service was down) and
makes crash-stranded claude sessions restorable again.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document that the latest-activity tie-break only ever resolves within the same worktree (worst case a sibling session, never an unrelated project), and that CLAUDE_CONFIG_DIR mirrors Claude Code's own config location. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds a claude-specific discovery module that scans encoded worktree transcript ChangesClaude backend session discovery and recovery
Possibly related PRs
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/backend-session-discovery.ts`:
- Around line 32-35: The directory enumeration can throw even after
existsSync(dir); wrap the readdirSync(dir) call in a try/catch and return null
on error so the routine preserves best-effort recovery. Concretely, replace the
direct for (const entry of readdirSync(dir)) loop with: try { const entries =
readdirSync(dir); for (const entry of entries) { ... } } catch (err) { return
null; } — keep the existing checks for entry.endsWith(".jsonl") and the best
variable ({ id, mtimeMs }) logic unchanged.
In `@src/multiplexer/runtime-state.test.ts`:
- Around line 334-335: The temp directory cleanup (rmSync(claudeHome, {
recursive: true, force: true })) is currently inside the try block so it won't
run if an assertion throws; move that rmSync call into the finally block so
cleanup always runs. Locate the test in runtime-state.test.ts where claudeHome
is created and ensure rmSync(claudeHome, { recursive: true, force: true }) is
executed inside the finally clause that pairs with the try surrounding the test
logic (preserve any existing error handling inside the try/catch).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: aad6432f-2e91-4f2c-87ec-459e88680073
📒 Files selected for processing (4)
src/backend-session-discovery.test.tssrc/backend-session-discovery.tssrc/multiplexer/runtime-state.test.tssrc/multiplexer/runtime-state.ts
- discoverClaudeBackendSessionId: readdirSync can throw (EACCES/ENOTDIR) even after existsSync; catch it and return null so a recovery miss stays best-effort instead of becoming a hard failure. - runtime-state test: move the temp-dir rmSync into finally so cleanup runs even if an assertion throws. Addresses CodeRabbit feedback on #100. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem (epic W2)
W0 reconnected go-forward backend-id capture, but it can't reach ids that were already lost before the fix, or lost while the project service was down. A crash that kills the tmux pane before capture leaves an offline agent permanently unresumable:
This is the residual memory↔disk divergence: the agent's real UUID still exists on disk (
~/.claude/projects/<encoded-cwd>/<uuid>.jsonl), but aimux's durable state lost it.Change
src/backend-session-discovery.ts—discoverBackendSessionId(tool, cwd)recovers a claude session's UUID from its own transcript store, scoped to the session's exact worktree so it can never bind an agent from another project. UUID-validated; picks the most recently active transcript (the one live at the crash). Codex carries its id in launch args, so only claude needs disk recovery.resumeOfflineSession— when the durable backend id is missing and the session isn't being relaunched fresh, discover it from disk, use it for an exact resume, and persist it via the new running session'ssaveState. If nothing is found, the existing refuse-to-guess behavior is preserved unchanged — discovery never weakens the "exact id only" guarantee (an unusable discovered id still throws).Safety
The original code deliberately refused to guess. This keeps that: discovery is cwd-scoped (never cross-project), uuid-validated, and gated behind
canResumeWithBackendSessionId. The only judgment-call trade-off — documented in the code — is that multiple sessions sharing one worktree resolve by latest-activity; worst case resumes a sibling in that same worktree (read-only history, recoverable), never an unrelated agent. That's strictly better than permanently stranding the session.Verification
yarn verifygreen: typecheck + lint clean, 1022/1022 tests.backend-session-discovery.test.ts(single/multiple/non-uuid/missing/tool-filter) + aresumeOfflineSessionlocking test proving a missing claude id is reconciled from disk instead of refused.Scope
Builds on W0 (merged). Recovers crash-stranded claude sessions (incl. the one that started this investigation). Does not touch the write layer (W1) or the CLI surface (W3).
🤖 Generated with Claude Code
Summary by CodeRabbit