Skip to content

[finding] check:pm-dispatch-gates runs 11m27s and buffers all output to the end — a foreground run is SIGTERMed at the container cap with zero diagnostic #14281

Description

@claude

Found while implementing #14004 (PR #14280). Filed rather than ridden along: it is a different surface than that card's declared one, and it is pre-existing — that PR adds roughly 90s to the figure below, it did not create the condition.

Measured

At 4b6dca186 (the #14004 branch), pnpm check:pm-dispatch-gates — which spawns scripts/pm/dispatch-gates.mjs --self-test — run under the shared verify lock:

✓ dispatch-gates self-test: 1174 cases pass.
os-verify-lock: VERDICT command-exit 0 · held the lock 687s (11m27s) · waited 1s

Two facts follow from that number on this container, both measured on this box today:

  1. A foreground run cannot finish. The container caps a foreground command at about 10 minutes and then SIGTERMs it. Two attempts at this gate died that way (exit 143) before it was moved to a detached run.
  2. A killed run yields ZERO diagnostic. selfTest() accumulates every case into an array and prints the whole battery only after the last case, so an interrupted run writes not one / line. Measured: a 9-minute foreground attempt produced 11 lines of output, all of them the lock wrapper's preamble, and zero case lines. A dev cannot tell a hang from a slow pass, and cannot tell which case was in flight.

Two more costs worth stating beside those:

  • It is the longest member by far of the gate family a scripts/** diff derives (the other 14 in that family each finish in seconds), so it dominates the local cost of any scripts/** card.
  • It holds the shared verify lock for its full 11m27s. One sibling agent was queued behind it for most of that hold during this run.

Why it matters

Every dispatch brief tells a dev to derive the gate union with dispatch-gates and run it. Followed exactly, on this container, that instruction produces a SIGTERM with no output on the one gate that guards the derivation tool itself — the failure mode the tool's own header calls out one level up: an instrument whose result cannot be read is indistinguishable from an instrument that was not run. In practice a dev either skips it (unmeasured, silently) or discovers detaching for themselves, which is a per-card cycle on the highest-cost gate in the family.

Note the cost is inherent rather than accidental: the battery re-runs the tool's real CLI as a child process many times, and a full derivation of this tree is about 30s a spawn. That is deliberate — the same header argues that only a real run can tell a live rendering from a pure-function pin — so shrinking the coverage is not obviously the right answer.

Options, not a decision

  • Stream each case verdict as it is decided rather than buffering to the end, so an interrupted run still reports how far it got and what failed. Smallest change; does not touch the runtime; turns a silent 143 into a partial reading.
  • Share one derivation across the end-to-end cases where the card under test is the same, cutting spawn count. Reduces runtime; risks coupling cases that are deliberately independent runs.
  • Record it as CI-measured in practice for local purposes — i.e. document that this gate is run detached, with the command to do it — and leave the runtime alone.

Unassigned and untriaged on purpose.

Dedup: full enumeration of the 436 open issues via the REST list endpoint with explicit &page=N (not the Link: rel=next cursor, per #13900), plus local grep. Positive control: #14004 present in the listing. Zero hits for pm-dispatch-gates; the three hits for the timeout/SIGTERM terms are #14213 (hook timeouts), #12337 (cold closure build under the same lock) and a PM seat post, none of them this. #13798 and #13799 are adjacent — self-tests that exit 0 early, and self-tests with no assertion floor — and both are about a battery that did not really run, where this is about one that runs correctly and cannot be read.

Generated by Claude Code


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions