Skip to content

Commit 3f4b696

Browse files
hotlongclaude
andauthored
docs(agents): os-dev waits actively on the shared verification lock (#8448) (#8464)
The measured stall: a dev queues on the shared verification lock, ends its turn expecting a wake-up from a process it does not own, and sits until the PM probes it. Nine instances across two shifts, all the same shape. - rule 1: the lock becomes a named convention -- path, flock-owns-the-release, wrap the command only, and a bounded acquire that fits inside one foreground call instead of a blind two-hour block. - rule 7 (new): queued is not stalled. Loop in-turn, spend the queue interval on lock-free work, and past ~20 minutes with no progress report blocked with the holder named. - rule 6: the "one legitimate long wait" sentence now says that wait is active and in-turn -- as written it read as permission to park on the lock. - terminating cleanly (new item): the end-of-turn self-check, scoped so the ordinary report is never a violation. Claude-Session: https://claude.ai/code/session_018WuTtyckQa1VcXwgd52JpN Co-authored-by: Claude <noreply@anthropic.com>
1 parent 76bcb83 commit 3f4b696

1 file changed

Lines changed: 30 additions & 4 deletions

File tree

.claude/agents/os-dev.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,16 @@ quote.
8080

8181
## Resource discipline — parallel agents share ONE container
8282

83-
1. **Serialize the heavy phase.** Wrap every build/test run in the shared verification lock:
84-
`flock -w 7200 /tmp/os-heavy-verify.lock -c '<command>'` (waiting on it is normal, not a
85-
hang).
83+
1. **Serialize the heavy phase — the shared verification lock is a named convention.** One
84+
lock per container, `/tmp/os-heavy-verify.lock`, wrapping every build/test run:
85+
`flock -E 99 -w 540 /tmp/os-heavy-verify.lock -c '<command>'`. Discipline: **`flock` owns
86+
the release** — the lock lives on an open fd and drops when the command's whole process
87+
tree exits, so never hand-roll a lockfile someone has to remember to delete; wrap **the
88+
command only**, never your reading, editing or deciding; and keep **`-E 99`**, without
89+
which a queue timeout and a genuinely failing test both exit 1. Bound `-w` to fit inside
90+
ONE foreground call (this harness caps a call at 10 minutes) and re-acquire in a loop: a
91+
`-w 7200` blind block cannot outlive the call it runs in, and backgrounding it to escape
92+
that cap is the stall rule 7 exists to stop. Queueing is normal, not a hang.
8693
2. **Cap the heap**: prefix heavy commands with `NODE_OPTIONS=--max-old-space-size=4096`
8794
(raise only with a reason).
8895
3. **Scope, don't sweep**: build/test the affected packages (`pnpm --filter <pkg> …`),
@@ -99,7 +106,19 @@ quote.
99106
them blocking, read the real output, continue. ⛔ Never park verification on a background
100107
watcher and stop — a completion notification is itself the statement that no live subtask
101108
remains, so that wake-up never arrives and the task sits stalled until the PM pulls it
102-
back. The one legitimate long wait is `flock` queueing in rule 1.
109+
back. The one legitimate long wait is `flock` queueing in rule 1 — and that wait is
110+
active and in-turn (rule 7), never a reason to stop.
111+
7. **Queued is not stalled — wait ACTIVELY, inside the turn.** Whatever holds the lock is a
112+
process you do not own, so nothing about its completion can wake you: ⛔ never end a turn
113+
to "wait for the lock". Measured, three agents in one batch ended on "the queued run will
114+
notify on completion" — none was ever notified; each cost the PM a probe round and its
115+
card 45–120 minutes. The loop instead: bounded acquire ⇒ on exit 99, spend the interval on
116+
lock-free work (test authoring, changeset, PR body, package-local `typecheck`) ⇒
117+
re-acquire. **Queued past ~20 minutes with no progress ⇒ stop and report `blocked` with
118+
the holder named**: `fuser -v /tmp/os-heavy-verify.lock` (or `lsof`) prints its PID and
119+
command, and an abandoned run's orphaned child keeps the lock until that child itself
120+
exits, so an unmoving holder is a real finding. Report it; silence is the one wrong
121+
answer.
103122

104123
## Toolchain traps (each cost at least one agent a false-red lap)
105124

@@ -302,6 +321,13 @@ your return message dies with your process; the comment is what survives you.
302321
a reprimand. On a probe, re-read state and deliver the report from your transcript: every
303322
such death so far was fully recoverable with zero work lost. The cost is latency, not
304323
correctness — ⛔ never "recover" by redoing the work.
324+
4. **The self-check before every turn you are about to end**: *does my last message describe
325+
a wake-up I expect from a process I do not own?* If yes — a queued lock, another agent's
326+
build, a watcher that already detached — that wake-up is not coming and you are about to
327+
stall; keep the turn alive and collect the exit code yourself. The report is never a
328+
violation of this check: it ends the turn on a **result**, `in_progress` gate status
329+
included, not on a promise that something else will resume you. The only wait you may end
330+
a turn on is one your report calls `blocked` and names.
305331

306332
## When to stop instead of code
307333

0 commit comments

Comments
 (0)