Skip to content

afx spawn --resume with a live builder-start.sh lineage creates duplicate live agents in one worktree #1322

Description

@waleedkadous

Incident (production workspace, 2026-08-01)

A wedged builder's claude PID was killed by the operator — the natural recovery move. Its parent .builder-start.sh loop (by design, for crash recovery) auto-respawned a fresh claude with the original spawn prompt, on the ORIGINAL terminal, unregistered with Tower. The operator's afx spawn <id> --resume then started a second, registered session. Both agents implemented the same plan phase simultaneously in one worktree until the registered one noticed foreign files appearing, reverted its own edits, and escalated. Resolution required a parent-first manual kill of the ghost lineage.

Two layers

  1. The respawn loop is intentional (crash recovery; Regression (3.2.4): double Ctrl-C on an agent no longer restarts it fresh — classified as deliberate quit #1264/[Bugfix #1267] Builder clean-exit relaunch reruns fresh instead of resuming the ended conversation #1317 refined its semantics: crash → resume-then-fresh, clean exit → fresh). But it converts "kill the wedged claude PID" into a ghost-session factory: the loop cannot distinguish an operator's deliberate kill from a crash. The sanctioned wedge recoveries are afx interrupt (ESC into the turn) and afx reset (save → clear → re-orient), which exist precisely so operators never signal PIDs directly — but nothing tells an operator that at the moment it matters.
  2. afx spawn --resume performs no liveness preflight: it consults registry state, not process reality, so it happily spawns into a worktree that already has a live (orphaned) lineage.

Asks

  1. Preflight on --resume (and plain respawn): scan for live processes whose command line references .builders/<id>/.builder-start.sh (and/or a live shellper bound to that worktree). If found: refuse, name the PIDs, and print the recipe — afx interrupt <id> / afx reset <id> for a wedge, or parent-first kill for a true orphan. --force overrides with eyes open.
  2. Make the loop operator-legible: when the child exits by signal, the relaunch banner should say what just happened and how to stop it ("child killed; relaunching per crash policy — to stop this loop, kill THIS script's PID first, or use afx interrupt/reset").
  3. Cross-link Terminal-session leak: builder respawns accumulate untracked shellper sessions that cleanup and the Builders view never see #1296 (session accumulation) — same family, worse consequence: this is the live-collision case rather than the zombie case, and Terminal-session leak: builder respawns accumulate untracked shellper sessions that cleanup and the Builders view never see #1296's reuse-or-reap principle covers both.

Credit

Reported by a production workspace architect with full PID/timestamp forensics; their builder's collision behavior (detect foreign edits → revert own → escalate) is the model response and worth encoding in builder guidance.

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