fix: crash recovery after server reboot (#16) #43
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/crash-recovery"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #16.
Problem
After a host reboot, workers came back broken in two ways:
exit 1. On a restart Nexus never re-injects, so withRestartPolicy: unless-stoppedthe container looped every ~30s and the operator couldn't evendocker execin.sessionsrows, making clean re-attach impossible.The hard constraint: the master key lives only in memory, so the vault is locked on a fresh Nexus boot — secrets can't be auto-re-derived.
Solution
Worker self-heals (no Nexus, no vault):
/workspace/.nexus/secrets.env(per-worker volume,chmod 600) after the clone, and re-sources it on restart. tmpfs stays as the first-spawn transport only (the clone needs/workspaceempty)./run/nexus/degraded, surfaced as adegradedbadge in the UI..nexus/is now excluded via the untracked/workspace/.git/info/exclude, so a fresh clone shows zero Nexus-induced changes (ensureNexusGitignoreremoved).Nexus restores sessions (
convergeSessions):convergeSessions()— the worktree is the source of truth: a missing window with an intact worktree is restored (relaunchclauderesuming the pinnedclaude_session_id); a row is deleted only when the worktree is positively confirmed gone. An in-flight guard prevents duplicate spawns; "couldn't check" never deletes.restoreSessionsOnBoot), onstartWorker, and on the health poll. Restored windows getNEXUS_URLre-injected (a degraded-boot tmux server lacks it).claude_session_id(migration 0012) and restores with--resume <uuid>.How it was built
Designed via brainstorm → spec (
docs/superpowers/specs/2026-06-22-crash-recovery-design.md) → plan (docs/superpowers/plans/2026-06-22-crash-recovery.md), implemented in 8 reviewed units (spec + code-quality review each; final whole-branch review passed).Test status
pnpm run typecheck→ 0 errors;pnpm test→ 541 passed;sh worker/secret-acquire.test.sh→ PASS (first-spawn / restart / degraded / fatal / info-exclude). New tests: migration 0012, resume-flag composition,convergeSessions(restore / confirmed-orphan-delete / never-delete-on-uncertainty / creating-skip / workspace_shell).Pre-merge checklist (cannot run here — no Docker/worker)
claude --remote-control "<id>" --session-id <uuid>, take a turn, kill it, relaunch with--resume <uuid>, confirm the conversation returns AND remote control re-registers (watch upstream caveat #60790). If--remote-controlignores--resume, the fix is a one-function edit inresumeFlag(already isolated + flagged in-code).docker restart <worker>→ no crash-loop, window returns with prior conversation,git pushworks, forgejo-mcp present, no degraded badge. Delete/workspace/.nexus/secrets.env+ restart → degraded badge, no loop.docker compose restart(reboot sim) → sessions auto-restore,notify-previewworks (NEXUS_URL injected).Notes
RestartPolicy: unless-stoppedis unchanged — it now does the right thing once the entrypoint stops crash-looping.Two Windows-only build failures: - better-sqlite3 ^11.7.0 has no prebuilt binary for Node 24, so pnpm compiled from source and failed: V8 headers now require C++20, which VS 2019 Build Tools cannot target. Bump to ^12.11.1, which ships a prebuilt win32-x64/Node-24 binary -- no local compiler needed. - The build:server script passed the $server esbuild alias as an inline CLI flag escaped for POSIX sh. Under pnpm's Windows shell the backslash survives, so esbuild rejected it ("Invalid alias name"). Move the bundling into scripts/build-server.mjs and use esbuild's JS API, where the alias is a plain string no shell touches. Identical behavior on Linux and Windows.vite.config.ts imports ./scripts/gen-migrations.mjs, but the Dockerfile copied src/static/migrations and the config files only -- never scripts/. So `pnpm run build` failed in CI when Vite loaded its config ("Could not resolve ./scripts/gen-migrations.mjs"). Add COPY scripts. This also covers scripts/build-server.mjs, which build:server now runs directly.