Skip to content

Tower recovery: Claude architect crash-loops on stale --resume with no user escape #1149

Description

@amrmelsayed

Problem

After a Tower recovery event (machine crash, tower start after downtime, network partition), some architect terminals enter a crash-loop displaying repeated "Could not resume session ..." errors. Users cannot cancel or dismiss the errors — the loop continues until Tower is externally killed or the offending row is manually removed from ~/.agent-farm/global.db.

Reported by users 2026-07-08.

Root cause (from subagent investigation 2026-07-08)

Both reconcile paths pre-compute restartOptions.args from the stored session id:

  • packages/codev/src/agent-farm/servers/tower-terminals.ts:661-690 (Phase 1 startup reconcile)
  • packages/codev/src/agent-farm/servers/tower-terminals.ts:898-925 (on-the-fly reconnect)

These args are handed to SessionManager.setupAutoRestart (packages/codev/src/terminal/session-manager.ts:845-891), which replays session.options.args verbatim after restartDelay (2s), up to maxRestarts (50 — tower-instances.ts:532).

If --resume <uuid> is unresumable (jsonl deleted, corrupted, or foreign), Claude Code prints Could not resume session ... and exits immediately. The exit re-triggers auto-restart with the same args. startRestartResetTimer fires unconditionally after each spawn() call (not after a "healthy" signal), so a fast-crashing process prevents the counter from ever resetting — count climbs monotonically to 50 (~100 seconds of errors) before removeDeadSession finally halts the loop.

No escape hatch exists — no env var, no CLI flag, no Tower HTTP endpoint. User must kill Tower or hand-edit global.db to break out.

Load-bearing context: the comment at packages/codev/src/agent-farm/utils/harness.ts:83-87 explicitly notes that codex/gemini omit session id to avoid this class of crash-loop. Claude has the vulnerability.

Fix sketch

Introduce a "recent failure" detector in setupAutoRestart (packages/codev/src/terminal/session-manager.ts:845-891):

  1. Track restart timestamps + exit statuses on the session object
  2. If ≥ 3 restarts fail within 30 seconds AND the failing args include --resume, strip --resume <uuid> from session.options.args on the next attempt
  3. Log once: "resume session <uuid> unrecoverable; retrying without --resume"
  4. Callback into state.ts to null out the stored session_id on the architect row so future reconciles don't relearn the poisoned id

Small change; matches the design intent of the harness.ts:83-87 comment (codex/gemini avoid this class; Claude should degrade gracefully into the same state).

Optional defense-in-depth: expose CODEV_SKIP_RESUME=1 env var read by resolveArchitectLaunch (tower-utils.ts:208-236) that strips --resume from launch args regardless of stored state. Emergency escape when the automatic fallback hasn't kicked in yet.

Scope

  • packages/codev/src/terminal/session-manager.ts — fast-failure detector
  • packages/codev/src/agent-farm/state.ts — helper to null-out session_id on architect row
  • Tests: simulate 3 fast failing spawns with --resume <bad-uuid>, verify auto-restart drops the --resume flag on attempt 4 and clears the DB row

Related

Activity

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

    area/towerArea: Tower server / agent farm CLI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions