Skip to content

The PUBLISHED pm-dispatch skill teaches a heavy-verify lock that serialises nothing: wrong lock path, and -w 7200 (13x the declared cap) #9922

Closed

Description

@os-warren

Filed unassigned while implementing #9661 (verify-lock mechanization, PR #9921). Out of
that card's scope: different surface, different gate family, and it was not in the
claim's declared file surface — recording rather than repairing.

What is wrong

skills/objectstack-pm-dispatch/SKILL.md (the published catalog copy), Resource
discipline item 1, line ~757:

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 '(build/test command)'
   (one lock file per container; waiting on it is normal, not a hang).

Two independent defects in that one line, and the first is the serious one:

  1. The lock path is wrong. The container's shared lock is
    /tmp/os-heavy-verify.lock. /tmp/heavy-verify.lock — no os- prefix — is a
    different file, so an agent following this recipe takes an exclusive lock that no
    sibling in the fleet ever contends on. It looks like serialisation, reports like
    serialisation, and serialises nothing. Verified on a live container: the real lock
    file is /tmp/os-heavy-verify.lock, and it is what .claude/agents/os-dev.md and
    every measurement on [finding] The shared verify lock starves convention-COMPLIANT agents: -w budgets differ 6x, and flock grants to whoever is currently waiting #9661 name.

  2. -w 7200 is 13x the declared acquisition cap. [finding] The shared verify lock starves convention-COMPLIANT agents: -w budgets differ 6x, and flock grants to whoever is currently waiting #9661 measured what an over-cap
    wait does to the fleet: a resident waiter beats an earlier-arriving compliant one 12
    times out of 12, purely by being present at the handoff. 7200 seconds is also twelve
    times the harness ceiling for a single foreground call, so a waiter that actually
    spent it would be killed mid-wait.

Sibling nit in the same section, item 4: git worktree remove (path) --force.
.claude/agents/os-dev.md rule 4 explicitly forbids reaching for --force first —
with node_modules already deleted, a refusal to remove means something in there is
uncommitted, and that refusal is the container's only guard for unpushed work.

Why it matters

This is the published catalog copy, i.e. the version outside users adopt, and it is
the one surface where the "declared but unenforced" shape cannot be fixed by a gate that
only reads .claude/. PR #9921 makes the internal convention structural
(scripts/pm/os-verify-lock.sh enforces the cap at the call site); this file is a
second, divergent copy of the same convention that the new entry point does not reach.

Not prescribing the fix here — whether the published skill should name a repo-specific
script at all, or state the property (one lock per container, a bounded wait) without a
copy-pasteable command, is a judgement for the skills lane.

Related to #9661.


Generated by Claude Code

Activity

  1. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Aug 19, 2026
  2. self-assigned this
    on Aug 19, 2026
  3. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    First-touch graded by the skills seat (own-lane exception; session session_01AeA3nU1B5Q2pgxqxgUrexd): PROMOTED → dispatch (this comment doubles as the claim), S/M. The fake-serialization defect is real and the published catalog is this lane's surface. Direction settled by the seat: the published skill states the PROPERTY, not a repo-specific command — the catalog ships into customer codebases where neither /tmp/os-heavy-verify.lock nor scripts/pm/os-verify-lock.sh exists, so any copy-pasteable literal is wrong-by-construction somewhere; the fix states the invariants (one shared heavy-verify lock per container; acquisition budget bounded inside one foreground call; queue-timeout distinguishable from command failure; hold duration observable) and defers the mechanism to the host repo's tooling, naming this repo's entry point only as an example of the shape. The --force worktree nit is in scope (align with the refusal-is-the-guard rule).

    Claim: PM loop round 6
    Session: session_01AeA3nU1B5Q2pgxqxgUrexd
    Branch: claude/issue-9922-published-lock-property
    Worktree: objectstack-issue-9922
    Domain: domain:skills
    File surface: skills/objectstack-pm-dispatch/SKILL.md only (stop on breach; explain in the report)
    Container & model: S/M, mode:subagent, model: opus — this dispatch's node scripts/pm/dispatch-gates.mjs --tier skills/objectstack-pm-dispatch/SKILL.md run: "no path-derived mandate … tier stays the PM's per-card judgment call" (the published copy is NOT under .claude/skills/pm-dispatch/**, so clause ① does not bind); PM call = opus
    Clause-②: no
    Serial constraints cleared: no open PR touches skills/** (open-PR list re-read this round); #9921 (the internal entry point) is draft-awaiting-merge on a disjoint surface — this card references its shape as an example, which is prose, not a file dependency.


    Generated by Claude Code

  4. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor
    {
      "issue": 9922,
      "status": "done",
      "branch": "claude/issue-9922-published-lock-property",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9924",
      "premise_still_valid": true,
      "summary": "Verified both defects on fresh main at line 757 and replaced the copy-pasteable flock recipe in skills/objectstack-pm-dispatch/SKILL.md (the published catalog copy) with four property statements: one shared heavy-verify lock per container (naming why a private path is the dangerous failure — it reports exactly like a lock that works), an acquisition budget bounded inside a single foreground tool call, a queue timeout distinguishable from the wrapped command's own failure, and observable hold duration so a stuck holder names itself. The mechanism is deferred to the host project and made structural, not prose-only, by a new row in the file's existing 'Adapting this loop to your project' conventions-file table. Item 4's force-first worktree removal is gone: force is now the last resort after diagnosing, since a refusal to remove is the container's only guard for uncommitted work. File surface held to exactly the one named file; no changeset (skills/ is in no package's published file list), skip-changeset applied and read back.",
      "tests": "All at final commit c3cc548a7 (tree clean; union re-derived AFTER the commit). `node scripts/pm/dispatch-gates.mjs` with no paths — derived 1 path vs merge base 05864fb20, three-dot semantics — matched 3 families, all run locally, each quoting the gate's own verdict line (exit codes captured before any pipe, via redirect-then-capture): check:nul-bytes → 'check-nul-bytes: OK (scanned 6304 text file(s) -- 6304 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).' exit=0; check:skill-frame-sync → 'check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files' exit=0; check:skill-compatibility → 'check-skill-compatibility-version: 11 SKILL.md file(s) reconciled against 77 workspace packages' exit=0; check:pm-governed-merges → 'check-governed-merges --self-test: 77 assertions' exit=0. Each self-test also run and green. Residue placed all 114 discovered families (3 matched / 35 undetermined / 76 silent). No ablation applicable — prose-only change, no code path, so nothing to mutate-and-rebuild. Post-edit rescan of the whole file for the defect family found ZERO residual literals: grep -nE '/tmp/|-w [0-9]+|--force|7200' over skills/objectstack-pm-dispatch/SKILL.md exits 1 (no match). Control-byte self-scan of the edited file: grep -naP over the ASCII control range, no match.",
      "line_budget": "NOT RATCHETED — the dispatch's mechanism assumption is FALSIFIED, measured not recalled. `pnpm check:pm-skill-ratchet` (scripts/pm/check-skill-line-ratchet.mjs) exits 0 and its CEILINGS map holds 16 entries: six .claude/skills/pm-dispatch/** files, six references/lanes/*.md, the four other .claude/skills/*/SKILL.md, .claude/agents/os-dev.md, and root AGENTS.md. ZERO entries under the published skills/ root — the gate's output does not mention objectstack-pm-dispatch at all (grep -c = 0), and dispatch-gates.mjs independently confirms it by not matching the ratchet family for this diff. So this PR pays no ceiling. Raw movement: 1050 → 1069 lines (+19); property statements are longer than the one broken command they replace, and the net-neutral-or-negative expectation was conditional on a ceiling that does not exist here. That coverage gap is filed as an observation, not repaired here.",
      "files_changed": ["skills/objectstack-pm-dispatch/SKILL.md"],
      "deviations": [
        "Did NOT cite scripts/pm/os-verify-lock.sh as the example-of-the-shape, though the adjudication permits it ('may', not 'must'). Evidence: the file does not exist on main — `ls -la scripts/pm/os-verify-lock.sh` → 'No such file or directory' at merge base 05864fb20. It lands with #9921, still draft-awaiting-merge. Naming it now would ship a dangling path in the PUBLISHED catalog, which is the exact failure mode this card removes. The shape is described generically instead; if #9921 merges and the maintainer wants the example named, it is a one-line follow-up. Recorded in the PR body under its own heading.",
        "Added one row to the file's 'Adapting this loop to your project' conventions-file table ('How the shared heavy-verify lock is taken (wrapper, lock path, acquisition budget)'). Same file, same defect class, same gate family — it makes the mechanism-deferral structural rather than resting on prose alone. Named here and in the PR body rather than landing unannounced."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #9923: the published skills/ catalog root carries no line-ratchet ceiling while every .claude/ instruction surface plus root AGENTS.md does — labelled `finding`, unassigned, no pm:queue; #9473 (closed) extended coverage to the four other internal playbooks but not to the published root, and skills/objectstack-pm-dispatch/SKILL.md at 1050 lines is larger than every ratcheted entry except AGENTS.md. Filed with both honest counter-arguments (the ratchet's stated rationale is seat-session token cost, and the published root is already human-merge-only), so it reads as an observation for triage, not a verdict."
      ]
    }

    Governed surface: PR #9924 opens draft and stays draft — never queued, armed, or flipped ready. Labels read back after write: size/s, skip-changeset. Worktree removed without --force (status clean first).


    Generated by Claude Code

  5. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    ACCEPT — reviewed against GitHub, not the report. PR: #9924

    • Diff verified by this seat's own fetch: exactly the one file. The broken recipe is replaced by four property statements, each carrying its rationale (notably: "a path only one agent uses serializes nothing, and it reports exactly like a lock that works" — the defect's own lesson, stated as the property); the mechanism is deferred to the host project structurally via a new conventions-file table row, not prose alone; item 4's force-first removal is inverted to refusal-is-the-guard. Post-edit family rescan of the file: zero residual literals.
    • Both deviations accepted with credit: refusing to cite os-verify-lock.sh while it exists only on a draft branch is exactly right — a dangling path in the published catalog is the failure mode this card removes (a one-line follow-up remains available after feat(scripts): mechanize verify-lock acquisition into a capped, FIFO, self-reporting entry point #9921 merges); the table row is same-file, same-defect-class, announced.
    • Mechanism assumption falsified and accepted: the published skills/ root carries no ratchet ceiling (measured — 16 CEILINGS entries, none under skills/), so the +19 lines pay nothing; the coverage gap is honestly filed as [finding] The PUBLISHED skills/ catalog carries no line-ratchet ceiling — every .claude/ instruction surface does #9923 (verified filed, finding, with its own counter-arguments — first-touch next round).
    • Gates: 3 derived families + self-tests green with quoted verdicts at head c3cc548.
    • Governed surface (skills/**) ⇒ terminal three-step: review recorded here; the PR stays draft, visibly awaiting the maintainer's merge; the round report lists it under "awaiting a human merge".

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions