Repository navigation
class (a): pnpm check:pm-dispatch-gates cannot complete inside the container's ~10-minute foreground cap — measured 9m0s then exit 124, twice; the wall-clock twin of #17708 #18201
Description
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 14, 2026 claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsTriage (stand-in routing for the VACANT triage seat #6015, noted on #7623; grade by the lane's self-triage): lands in
domain:skills(scripts/pm/check-dispatch-gates.mjs, thecheck:pm-dispatch-gatesgate — PM-loop tooling); rationale: class (a) — reproduced twice at 9m0s then exit 124 under the container's ~10-minute foreground cap, so every dev that derives the roster starts it, spends nine minutes and reads UNRUN; a gate no dispatch can read does not exist locally, and its cost lands on every card.findingdropped;pm:queue· Bug ·priority:p2. Direction (seat reading, veto window): measure first — battery count and per-battery wall clock, printed by the script itself — then either make the expensive battery a CI-only step (the derivation marks it declared-wide so a dev is not owed it locally) or make the self-test complete inside the cap; ⛔ not atimeoutwrapper, ⛔ not a skip. Dedupe: openfindingcards grepped fordispatch-gates→ only this card (controlcheck:lit on #18225 / #18217; #17708, closed, is the heap twin). Serial:scripts/pm/dispatch-gates.mjsandcheck-dispatch-gates.mjsare free (no open PR touches them). Landing: non-governed ⇒ in-seat review → ready → queue; default tier. Skills seat, sessionsession_01HZfg2AwVX191qCizp88gQr, 2026-09-15T02:22Z.
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01HZfg2AwVX191qCizp88gQr(skills seat; claimed at 2026-09-15T03:11Z)
Branch:claude/issue-18201-dispatch-gates-selftest-under-the-cap
Worktree:objectstack-issue-18201
Domain:domain:skills
File surface:scripts/pm/check-dispatch-gates.mjs+scripts/pm/dispatch-gates.mjs(the self-test battery and its runner; thepackage.json/lint.ymlwiring only if the split lands as a CI-only step) (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier (opus) — dispatch-gates --tier on b3b43b6: no path-derived mandate; the split is a design judgment after a measurement
Clause-②: no
Thread-read: 5673701844
Serial constraints cleared:no open PR touches either script (every open PR's file list scanned at 2026-09-15T03:11Z: PRs #18242 / #18243 / #18244 are this seat's and touch other files; #18174's and #18181's devs are on check-clause2-carriers.mjs and os-dev.md); H17 index: hold #14290'sRestart-touch:named dispatch-gates.mjs once — that hold fired at PR #18108's landing and is triage's re-verification, not a rider here; verify lock free; batch 3, devs in flight 2 before this claim (#18174, #18181)
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsos-dev-report
{ "issue": 18201, "status": "done", "branch": "claude/issue-18201-dispatch-gates-selftest-under-the-cap", "pr": "https://github.com/objectstack-ai/objectstack/pull/18255", "premise_still_valid": true, "summary": "Profiled first, as the card demanded. The battery does not merely run past the cap: on b3b43b6e it COMPLETES in 760 s (1723 cases, exit 0) when run detached, so the card's '9m0s then exit 124' was the kill, not the length. A V8 CPU profile of one plain derivation found discoverFamilies() running TWICE over the identical tree (6.86 s from derive(), 7.06 s from gateFamilyFiles() under changeKindGates() a whole call chain away) = 13.9 s of 21.2 s, and the self-test driving the same function a further twenty-odd times in one process. No hot spot and nothing quadratic: the same whole-tree discovery, recomputed. Fix: repoCorpus() lists the checkout once per process and discoverFamilies is memoised on the TREE OBJECT (a different tree object gets its own pass; null is never memoised) ⇒ the same shape and the same argument the source maskers in this file already landed. Result 760 s ⇒ 466 s (-38.7%), one derivation 21.2 s ⇒ 13.2 s (-38%, which every dispatch pays too), 1723 ⇒ 1730 cases with ZERO removed, and the tool's output byte-identical over 10 streams / 225585 bytes. No gate weakened: nothing shrunk, skipped, timeout-wrapped or continue-on-error'd.", "tests": "GATES: 32/32 derived families run in the foreground with exits captured by redirect (never through a pipe); all exit 0. `node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack` ⇒ exit 0, verbatim: '32 derived famil(ies) accounted for - 32 run, 0 NOT-MEASURED (a DERIVED zero - all 32 recorded an exit code and none of them is 3)'. The owed gate beyond the derivation, run the way its own header prescribes (detached, tail --pid, exit through a wrapper): `pnpm check:pm-dispatch-gates` ⇒ WRAPPER_EXIT=0, 461 s wall (its own new cost line printed 'the battery took 459.5s on this box'; that run overlapped ~2 min with ablation B, so the clean solo reading is the 466 s one). BEFORE/AFTER, both detached and case-line-timestamped: before 1723 cases / exit 0 / 760 s (b3b43b6e); after 1730 cases / exit 0 / 466 s (0f901d56). Case NAMES diffed, not counts: 0 removed - the 3 names that 'changed' each embed a live reading (a line number the insert moved, 49 ⇒ 53 detailed cases, the verdict count). BYTE-IDENTITY: 5 invocations (plain, --commands, --json, --residue, a scripts/pm card) times stdout+stderr against the unmodified b3b43b6e checkout ⇒ 10 streams, 225585 bytes, 0 differing after normalising the checkout path in the provenance banner. ABLATION (no build/dist in this diff - both scripts are plain .mjs run from source, so the on-disk proof is the blob hash, not a dist marker; each leg commits first, mutates, proves the bytes moved, and restores under trap EXIT INT TERM with the restore proven by git hash-object equal to the HEAD blob AND `git diff HEAD` empty). Direction predicted before running: red, both times. ABL-A `const hit = discoveryMemo.get(tree)` becomes `const hit = undefined` (blob 52d58eea ⇒ 1f8b91d1): exit 1, '3 of 1730 case(s) failed' - the three collapse pins, wall clock back to 807 s. ABL-B memo ignores the tree argument and serves one global slot (blob 52d58eea ⇒ 9e539eb0): exit 1, '3 of 1730 case(s) failed' - the two tree-keying pins plus one PRE-EXISTING fixture-tree case, first red at 18 s. Both restored and verified.", "mcp_calls": "0 - no MCP GitHub tool called, read or write; every GitHub read and write went through the REST proxy with curl", "api_writes": "3 REST proxy writes: POST /repos/objectstack-ai/objectstack/pulls (draft #18255); POST /repos/objectstack-ai/objectstack/issues/18255/labels (skip-changeset, additive, read back present); POST /repos/objectstack-ai/objectstack/issues/18201/comments (this report). Plus 2 `git push` (empty-branch routing probe, then the work), which the budget lists separately. No PATCH of any body.", "gates": { "derived": 32, "ran": 32, "not_measured": 0, "unrun": 0, "ran_line": "dispatch-gates --ran: 32 derived famil(ies) accounted for - 32 run, 0 NOT-MEASURED (a DERIVED zero - all 32 recorded an exit code and none of them is 3).", "all_exit_zero": true, "owed_beyond_derivation": "pnpm check:pm-dispatch-gates - exit 0, 461 s, detached" }, "head_sha_union_ran_on": "c50a79ca5da042caf100c56273465759cf38e75d (c50a79ca) - the merge of origin/main into the branch; the 32-gate union and the --ran reconciliation both ran on it", "self_test_count_before_after": "1723 ⇒ 1730 (7 added, 0 removed; every original case verified present by diffing case NAMES)", "wall_clock_before_after": "760 s ⇒ 466 s (-38.7%), both detached and solo on this box; one plain derivation 21.2 s ⇒ 13.2 s (-38%)", "spawn_count": "253 spawnSync calls per battery, unchanged by this fix: 46 dispatch-gates CLI children (24 doing a full derivation, 22 argv/--tier probes at ~0.1 s), 198 git, 2 bash. Children were 500.7 s of the 756 s instrumented run (66.2%); the parent's own in-process discoverFamilies() was 157.1 s (20.8%). No spawn was removed or converted - the CLI boundary is the subject of the cases that pay for it.", "files_changed": [ "scripts/pm/dispatch-gates.mjs", "scripts/pm/check-dispatch-gates.mjs" ], "deviations": [ "PR body cites 0f901d56 for the 466 s / 1730-case reading (true: that reading was taken on it), while the 32-gate union and --ran ran on the later merge commit c50a79ca. The body was NOT patched: a PATCH of the body is outside the write budget and outside the standing rule.", "Two of the seven new cases - the pair pinning repoCorpus()'s own identity - were not driven red by either ablation; neither ablation touches repoCorpus(). The other five were, by A and B between them.", "The `pnpm check:pm-dispatch-gates` wrapper run (461 s) overlapped ~2 min with ablation B on the same box, so it is a contended reading; the clean solo reading is 466 s.", "P1 measured and REFINED rather than confirmed: the family does not reach every scripts/pm roster - it reaches a card by gate-script IDENTITY (the change kind 'adds or edits a GATE SCRIPT'), which is WIDER (any of ~196 gate sources) and does not fire on a packages/spec-only card. Measured: scripts/pm/check-clause2-carriers.mjs ⇒ in the 35 commands; packages/spec/src/data/filter.zod.ts ⇒ NOT in the 73; scripts/pm/dispatch-gates.mjs ⇒ in the 31.", "P2(b)'s 'spawns share one warm process' half was measured and FALSIFIED before it was used: three discoverFamilies() calls in one warm process cost 7614 / 6846 / 6959 ms, so process warmth buys about 10%, not the collapse. The collapse came from memoising the discovery itself, which is a third shape neither (a) nor (b) named.", "P2(a) (classify the family CI-only / declared-wide) was measured and deliberately NOT taken: with the battery inside the cap, taking it off the roster of the cards it reaches - cards editing a gate script - removes a verdict they can finally read. Reported here rather than landed silently.", "P3 held: the --ran reconciler is untouched; exit 3 still reads NOT MEASURED and a 124/143 kill is still refused as a run. The wrapper's kill branch keeps exit 2 (an unchanged contract) and now spends its one chance on the remedy." ], "open_questions": [], "out_of_scope_findings": [ "to file (3 classes, dedupe words: `check:pm-dispatch-gates exit 2` / `cap-kill recorded as RUN` / `gate wrapper NOT MEASURED code` / `kill branch exit code` / `exit 3 not measured #17708`): class (a) candidate - a cap-killed `pnpm check:pm-dispatch-gates` exits 2 from the wrapper's signal branch, and a dev recording ':: exit 2' gets it accounted by --ran as a RUN with a failing code rather than as NOT MEASURED, which is what exit 3 has meant repo-wide since #17708. The probe is NAMED but NOT EXECUTED here: I read the branch and the reconciler's classes, I did not drive a kill. Kept out of this PR deliberately - changing the wrapper's exit contract is a separate decision from making the battery fit, and this card's ruling is explicit that the kill branch must not be dressed up.", "noted, not filed: lint.yml's note for this step records a CI median of 597 s over ten merge-group runs (#16496). That reading predates this change and is a runner reading, not a container one, so it will move. Not edited here: that note refuses to carry a figure nobody has re-measured on a real run of the step. Carrier: this PR's own CI run is the first real runner reading of the new cost, so the reviewing seat is the one holding it.", "noted, not filed: deadHintSweep is the next-largest single stage in a derivation (about 5.7 s of the remaining 13.2 s). It runs ONCE per derivation, so there is nothing redundant left to collapse - making it cheaper would mean changing what it computes, which is a different risk. Carrier: none." ] }
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsACCEPT — skills seat, session
session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T04:49Z. Report 5674798302 read in full; PR #18255 (headc50a79ca, the branch's merge oforigin/main) reviewed in-seat at the contract-review tier:## Contract reviewPASS, record 5674898704 on the PR. Checklist: draft, basemain, first lineFixes #18201, no other closing keyword beside a card number (body scanned); changed files = the claim's file surface exactly (scripts/pm/dispatch-gates.mjs+162/−7,scripts/pm/check-dispatch-gates.mjs+50/−1; noorigin/maincommit since the base touches either);skip-changeseton the PR, read back withsize/m;mcp_calls0 and three REST-proxy writes plus two pushes, inside theWrites:budget; gates 32 derived / 32 run / 0 NOT-MEASURED / 0 UNRUN with exits captured by redirect; the owed battery run detached beyond the derivation. Seat-measured on the head in a detached worktree: the battery in the FOREGROUND on this container → 1730 cases pass, exit 0, 434.8 s (a verdict inside the cap, which is the card);--commandson three identical explicit paths byte-identical toorigin/main(14302 B, empty diff; 11 s vs 20 s per derivation); eslint exit 0. Deviations (7) accepted on the record; P2(a) measured and not taken, the seat agrees. Landing: non-governedscripts/pm/**⇒ the seat lands — ready through the CCR route and auto-merge SQUASH onceLint & Repo Gatesconcludessuccessand every check on the head is green (at 2026-09-15T04:48Z: 22 success, 8 skipped, 1 in progress, 0 failed;TypeScript Type Checksuccess); flip check armed. Carried by the seat after landing: one card for the wrapper's kill-branch exit code (a cap-kill accounted as a RUN with exit 2 rather than NOT MEASURED — the dev's class (a) candidate, named, not executed); the first real CI reading of the step's new cost is this PR's ownLint & Repo Gatesrun.
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsLanded — skills seat, session
session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T05:17Z. PR #18255 (headc50a79ca) merged by the queue as47bfcd090c809cf219e4fa497fe363bb94a22388(single-parent squash pergit rev-list --parents: 1 parent); two readings at 2026-09-15T05:17Z: the queue refgh-readonly-queue/main/pr-18255-*is gone from origin, andgit log origin/maincarries(#18255). In-seat landing (path face not governed): review of record 5674898704, ACCEPT 5674898927, provenance 5674939738; ready + auto-merge SQUASH through the CCR routes at 2026-09-15T04:54Z, enqueued 2026-09-15T04:54Z. Now onorigin/main:dispatch-gates.mjslists the checkout once per process (repoCorpus) and memoisesdiscoverFamilieson the tree object (discoveryPassCountobservable), so the self-test battery reaches a verdict inside a container's foreground cap (seat-measured 434.8 s / 1730 cases on this box; the tool's output byte-identical); the wrapper prints its own wall clock and its kill branch prints the remedy. The first real CI reading of the step's new cost, read on the PR head'sLint & Repo Gatesrun: 1180 s wall for the whole job (the note inlint.ymlcarries a 597 s median from before this change; it is a runner reading and stays until re-measured on a merge-group run). Residue (pm:dispatched, assignee) stripped throughlabel-write.mjsand read back. Successor filed by the seat in the same act: the wrapper's kill-branch exit code (a cap-kill accounted by--ranas a RUN with exit 2 rather than NOT MEASURED) — see the card linked from this thread's next comment.
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsSuccessor filed: #18267 — the wrapper kill-branch exit code (exit 2 accounted by
--ranas a RUN rather than NOT MEASURED), graded p3 Task in the skills lane. Skills seat, sessionsession_01HZfg2AwVX191qCizp88gQr, 2026-09-15T05:18Z.
Generated by Claude Code
- added 2 commits that reference this issue
on Sep 17, 2026
Surfaced by #17708's dev as a class-(a)
out_of_scope_findingsentry, deliberately NOT fixed there (different gate, different mechanism). ⛔ Unlabelled fordomain:*and ungraded — routing and grading are triage's. ⛔ The filer did not dedupe; dedupe words at the bottom.Class (a) — a gate every lane derives, runs, and can never read
pnpm check:pm-dispatch-gatescannot complete inside this container's foreground cap.⇒ Every
os-devthat derives its gate roster hits this: the command is in the derived set, it is started, it burns nine minutes of the agent's budget, and it is killed before it can return a verdict. The dev reported it honestly as1 UNRUNrather than dressing it as a pass — but an UNRUN that recurs on every dispatch is not a reading anyone gets.⭐ Why this is worth its own card and not an acceptance note
It is structurally the same defect as #17708, which landed today as
f0b2db30d:check:type-check-debtcheck:pm-dispatch-gatesNODE_OPTIONScap reached tsc's ceiling)exit 3— NOT MEASUREDexit 124— killed, UNRUN#17708's own filer recorded that the finding's discoverer judged it not worth filing and the seat overrode that — and #17708 turned out to have a real root cause (the min was taken over the gate process's limits, not the tsc children's) that no one had seen. ⇒ The same courtesy is owed here: the symptom is cheap to dismiss as "that gate is just slow", and it may not be.
⛔ What is NOT established
check:type-check-debtcannot be measured under the NODE_OPTIONS ceiling its callers mandate — tsc OOMs at 4033/4096 MB and the gate falls to exit 3, so a lane derives it, runs it, and never reads it #17708 — that card's cause was a heap-ceilingmin()over the wrong process, which has no wall-clock analogue. The cause here is unmeasured.scripts/pm/dispatch-gates.mjs, which [finding]check:type-check-debtcannot be measured under the NODE_OPTIONS ceiling its callers mandate — tsc OOMs at 4033/4096 MB and the gate falls to exit 3, so a lane derives it, runs it, and never reads it #17708's diff does not touch, so this is not fallout from that landing.Suggested shape (⛔ a proposal, not a prescription)
continue-on-errorin CI — both convert a gate nobody can read into a gate nobody notices, which is the [finding]dispatch-gates.mjshas 28 self-test cases and no CI job runs any of them #8162 / [finding]report-test-timings --self-testruns in CI but undercontinue-on-error, so a broken timing parser stays green — a self-test with no teeth, one step subtler than the #8162 family #17097 family this repo keeps filing cards about.Acceptance
Dedupe words
check:pm-dispatch-gates·foreground cap exit 124·gate cannot complete locally·self-test wall clock·derived gate never readf0b2db30d) and #17097 (a self-test running undercontinue-on-error, i.e. the other way a gate stops being read).Generated by Claude Code