From c3cc548a73f8b30afe34b071508279b0dd3a9114 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 08:06:23 +0000 Subject: [PATCH] docs(skills): state the heavy-verify lock as properties, not a broken recipe The published pm-dispatch catalog copy taught a copy-pasteable `flock` line whose lock path had no `os-` prefix -- a private file no sibling ever contends on, so it serialized nothing while reporting exactly like a lock that works -- and a `-w` budget 13x the declared acquisition cap and 12x the ceiling on a single foreground call, so a waiter that actually spent it would be killed mid-wait. The catalog ships into customer codebases where neither that path nor this repo's wrapper exists, so any copy-pasteable literal is wrong-by-construction somewhere. Item 1 now states the invariants instead: one shared heavy-verify lock per container; an acquisition budget that fits inside a single foreground tool call; a queue timeout distinguishable from the wrapped command's own failure; an observable hold duration so a stuck holder names itself. The mechanism is deferred to the host project and made structural by a new row in the "Adapting this loop to your project" table. Item 4 drops the `--force`-first worktree removal: with dependencies already deleted, a refusal to remove is the container's only guard for uncommitted work, so force is the last resort after diagnosing, not the recipe. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd --- skills/objectstack-pm-dispatch/SKILL.md | 33 +++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/skills/objectstack-pm-dispatch/SKILL.md b/skills/objectstack-pm-dispatch/SKILL.md index 832aaa65df..f56e5c19c0 100644 --- a/skills/objectstack-pm-dispatch/SKILL.md +++ b/skills/objectstack-pm-dispatch/SKILL.md @@ -752,18 +752,36 @@ most often get missed: Resource discipline — parallel agents share ONE container; unbounded build and test runs exhaust it. Binding: -1. Serialize the heavy phase. Wrap every build and test run in a shared - container-wide lock so editing parallelizes but memory peaks never stack: - flock -w 7200 /tmp/heavy-verify.lock -c '' - (one lock file per container; waiting on it is normal, not a hang). +1. Serialize the heavy phase. Editing parallelizes; build and test runs must + not — every one of them is wrapped in a lock the whole container shares, so + memory peaks never stack. The MECHANISM is the host project's (its wrapper, + its lock path, its budget — put it in the conventions file); the properties + that make any such wrapper correct are not negotiable: + - ONE shared heavy-verify lock per container, so every agent contends on + the same file. A path only one agent uses serializes nothing, and it + reports exactly like a lock that works. + - The acquisition budget fits inside a SINGLE foreground tool call: waiting + must never outlive the call carrying it, and backgrounding a wait to + escape that ceiling produces the stall the lock exists to avoid. + - A queue timeout is distinguishable from the wrapped command's own + failure — "I never got the lock" must not read as "the tests failed". + - Hold duration is observable, so a stuck holder names itself instead of + being inferred from everyone else's queueing. + Queueing is normal, not a hang. Queueing with no end in sight is a finding: + report it, naming the holder. 2. Cap the heap: prefix heavy commands with NODE_OPTIONS=--max-old-space-size=4096 (raise only with a reason). 3. Scope, don't sweep. Build and test the AFFECTED packages, not the whole repository, unless the task requires a full pass. Cap test parallelism (e.g. vitest --maxWorkers=2). -4. Clean up: after the PR is up, remove your worktree - (git worktree remove --force). Leftover dependency trees exhaust the - container's disk, which fails as confusingly as running out of memory. +4. Clean up: after the PR is up, delete the worktree's dependency tree and + then remove the worktree. Leftover dependency trees exhaust the container's + disk, which fails as confusingly as running out of memory. Do NOT force the + removal as the opening move: with dependencies already deleted, a refusal + to remove means something in there is uncommitted — your own unpushed work, + or another agent's tree if the path was mistyped — and that refusal is the + container's only guard for it. Read the refusal first; force only after the + answer is genuinely "nothing". 5. NEVER kill a process by name. A name-matched kill (pkill -f ) can take down a parallel agent's run. Record the PID of what you start and operate on that PID only (kill $PID; liveness via kill -0 $PID). A pgrep pattern can @@ -1040,6 +1058,7 @@ them into every dispatch: | Required release-note artifact (changeset, CHANGELOG entry, none) | conventions file | | Files owned by a release process that a code PR must never touch | conventions file | | Test / typecheck / lint commands per package | conventions file | +| How the shared heavy-verify lock is taken (wrapper, lock path, acquisition budget) | conventions file | | Merge policy (merge queue, serial merge, maintainer-only) | conventions file | | Capability-expansion stance the business-need axis reads (tight by default, or permissive) | conventions file | | Which repositories exist and which is the backlog | `.claude/pm-dispatch.json` |