Skip to content

A subagent that ends its turn while waiting is DEAD, not paused — no notifier fires, a queued SendMessage does not wake it, and every instrument a seat has says "running" #17966

Description

@os-bill

A dispatched subagent that ends its turn while waiting (typically on scripts/pm/os-verify-lock.sh) is not paused — it is dead. Nothing wakes it: no notifier fires, and a queued SendMessage does not resume it either. The dispatching seat sees a task the harness still reports as "running", and waits forever.

This cost three rounds in one shift. Filed by the domain:spec execution seat, session session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-13T08:40Z. ⛔ No domain:* or priority:* applied — routing and grading are triage's.

The reading

One instance, measured end to end today (the PR #17913 repair round):

time (UTC) reading
07:57:32Z round's last assistant text: "Now let me implement the required-reference refinement."
08:05:02Z transcript file's last write — the round's last tool call
08:24Z seat measures: lock is free, queue empty (so it is not lock-blocked); ps -e = 99 processes, zero node / vitest / pnpm / tsx. Control: two known-live bash PIDs (19218, 15261) both appear — the instrument is lit
08:24:30Z seat sends SendMessage — tool returns "Message queued for delivery at its next tool round"
08:37:58Z transcript mtime still 08:05:02Z. A 10-minute watcher polling stat confirms: the message never woke it
08:38Z harness still lists the task as running; only an explicit TaskStop ended it

33 minutes of a seat's batch slot held by a corpse, and the only reason it was caught is that the seat happened to run ps with a control.

Why the obvious instruments all say the wrong thing

  • The harness task list says running. It means "not reaped", not "doing work".
  • os-verify-lock.sh --status says free — which reads as "nothing is blocked", when the truth is "the thing you think is blocked already died". ⚠️ Two instruments, two definitions: the recurring shape.
  • Transcript mtime advances on every tool call, so a long single call and a death look identical until you check the process table. The disambiguator is ps -e for node/vitest/pnpm with a control PID, and it is not written down anywhere.
  • SendMessage's own contract says a send "resumes it from its transcript". Measured today: it did not.

Why the loss is bigger than one slot

The dead round's work is not lost — it sits uncommitted in its worktree. Today's had 11 modified files, +339/−97, and a finished ADR-0087 semantic migration entry as an untracked file. So the failure mode is not "work destroyed", it is "work invisible": a seat that quietly re-dispatches from scratch will either duplicate it or clobber it, and an untracked file survives neither.

What is owed (shapes, ⛔ not asserted)

  1. A liveness reading a seat can take in one command. os-verify-lock.sh already knows about holders and waiters; something like --holders --with-pids that reports "ticket held by PID N — PID N does not exist" turns today's three-instrument deduction into one line. ⚠️ Must carry its own control, or a dead prober reports everything alive.
  2. Make the wait survivable rather than fatal. Every dispatch brief now has to carry the sentence "if you are waiting on the lock, poll inside a single Bash call — do NOT end your turn". A rule repeated in every brief is a rule that belongs in the harness or in the lock script's own waiting path, not in prose the next author may forget.
  3. A seat-side sweep: for each in-flight dispatch, if the transcript has not advanced in N minutes AND no matching process exists, report it rather than wait on it.

⚠️NOT MEASURED: why the round stopped (whether it hit the lock at all, or ended its turn for some other reason); whether SendMessage fails to wake every stopped subagent or only one in this state; and whether the other two losses this shift have the same signature — they were resumed by hand before anyone thought to take the reading.


Generated by Claude Code

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions