Repository navigation
Description
Found while working #13757 (PR #13952). Out of scope there — that card is a docblock relabel
and is fenced to not touching the gate's logic — so recording it here instead.
What happens
scripts/check-release-section-coverage.mjs --self-test reads its sibling gate to pin the two
scope floors together. The path is repo-root-relative:
const SIBLING_GATE = 'scripts/check-release-page-status.mjs';and the read sits inside a try { ... } catch { /* reported by the expect below */ }. So when
the process cwd is not the repo root, the read fails, siblingFloor stays null, and the
self-test reports a content verdict about the other gate:
x self-test: scope — the floor is READ from scripts/check-release-page-status.mjs and equals
this gate's (sibling: UNREADABLE, here: 16). Two gates reading the same pages under different
floors is two answers to one question, so the inheritance is pinned rather than declared in a
comment
check-release-section-coverage --self-test: 1 failure(s).
Repro — same commit, same clean tree, two cwds
$ cd /path/to/objectstack && node scripts/check-release-section-coverage.mjs --self-test
OK self-test: 41 cases pass ... # EXIT=0
$ cd /path/to && node objectstack/scripts/check-release-section-coverage.mjs --self-test
x self-test: scope — ... (sibling: UNREADABLE, here: 16) ... # EXIT=1
Verified at 71018fe16 with git status --porcelain empty.
Why it is worth a card
The failure is not "I could not read a file" — it is a confidently wrong verdict about a
different gate, in the exact prose a real cross-gate floor drift would produce. A reader who
hits it goes looking for drift in check-release-page-status.mjs that is not there.
It is the family of #13885 (a gate signalling "nothing measured" with the exit code the repo
reserves for a real finding) and #13798 (self-tests that exit without reaching their verdict),
one step worse: this one reaches its verdict and the verdict is false. AGENTS.md already names
this class — a PREREQUISITE NOT MET / could-not-measure state must read as NOT MEASURED, never
as a finding. check-test-completeness.mjs is the in-repo model: it exits 3, distinct from
a finding's 1, and says in its own output that it is not a finding.
Cost paid: one ablation cycle on #13757 was scored against this false red before the cwd was
identified as the cause. The reading was void, not a measurement.
Possible remedies (not ruling between them)
- Resolve
SIBLING_GATEfromimport.meta.urlrather than cwd, so the pin is cwd-independent.
Note AGENTS.md's cross-package-test-inputs spellings if that path would escape the package. - Keep the relative path but drop the
catch, and let an unreadable sibling be a loud
prerequisite failure with its own exit code, in thecheck-test-completenessshape — so
"cannot read" never renders as "the floors disagree".
Option 1 removes the failure mode; option 2 keeps the verdict honest when it fires. They are
not exclusive.
Worth checking whether sibling gates in scripts/ share the shape — this was found by
accident, not by a sweep.
Generated by Claude Code
Generated by Claude Code
Activity
baozhoutao commentedon Sep 1, 2026
Claim: PM loop round 1
Session:
session_01WLJQhde67SeTccsmnBVarVBranch:
claude/issue-13954-cwd-false-verdictWorktree:
objectstack-issue-13954Domain:
domain:devxFile surface:
scripts/check-release-section-coverage.mjsonly — the card is deliberately fenced to one file; the "do sibling gates share the shape" sweep is another card per the triage ruling (stop on breach; explain in the report)Container & model: M,
mode:subagent,model: claude-opus-5—node scripts/pm/dispatch-gates.mjs --tier scripts/check-release-section-coverage.mjsreturns "no path-derived mandate"; tier is this seat's judgment: the remedy applies a settled exit-code-3 pattern but restructuring a self-test's failure channel is judgment work.Clause-②: no
Serial constraints cleared: no in-flight claim touches
scripts/check-release-section-coverage.mjs(round opened with zero in-flight; lane inventory checked). Family siblings #13885/#13798/#13757 are distinct mechanisms per triage; #13757's PR #13952 already landed or is out of this file — verified no open PR claims this path.Verified at dispatch time on
origin/main:SIBLING_GATEbare repo-root-relative path at:151, the swallowingcatchat:437— the card's premise still holds.Generated by Claude Code
baozhoutao commentedon Sep 1, 2026
Dev claim (os-dev seat, dispatched by PM loop round 1).
Session:
session_01WLJQhde67SeTccsmnBVarVBranch:
claude/issue-13954-cwd-false-verdict(empty branch pushed at base2bff79ae381d72c38c30ac9695eed429f1b61761)Worktree:
../objectstack-issue-13954File surface:
scripts/check-release-section-coverage.mjsonly — the triage ruling fences this card to one file; the "do sibling gates share the shape" sweep is another card and is NOT folded in here.Plan per the triage ruling (both remedies, not one):
SIBLING_GATEfromimport.meta.urlso the pin is cwd-independent.catchsemantics — an unreadable sibling becomes a loud PREREQUISITE-NOT-MET with its own exit code in thecheck-test-completeness.mjsshape (exit 3, distinct from a finding's 1), never rendered as "the floors disagree".Both directions will be observed: the false red reproduced from a non-root cwd before the fix, the same invocation green after, and the loud prerequisite path fired deliberately.
Generated by Claude Code
Generated by Claude Code
baozhoutao commentedon Sep 1, 2026
os-dev-report
{ "issue": 13954, "status": "done", "branch": "claude/issue-13954-cwd-false-verdict", "pr": "https://github.com/objectstack-ai/objectstack/pull/14160", "premise_still_valid": true, "head_sha": "d6fdbb5e", "files_changed": ["scripts/check-release-section-coverage.mjs"], "summary": "Premise reproduced exactly before touching anything: same clean tree, exit 0 from the repo root and exit 1 with the false floor-mismatch verdict from a non-root cwd (codes captured with no pipe). Both triage-ruled remedies landed in that one file. (1) SIBLING_GATE is now resolved from import.meta.url into an absolute SIBLING_GATE_PATH, while the repo-relative name is kept for message text. (2) The swallowing catch is replaced by readSiblingFloor(), which answers a floor OR an unmet prerequisite with no third carry-on value, and siblingFloorRefusal(), which exits 3 in the shared PREREQUISITE NOT MET frame (check-test-completeness.mjs / import-prerequisite.mjs) before the first case runs, stating in words that it is not the claim that the two gates floor at different majors. Exit codes are named constants; only the prerequisite branch takes a code of its own. Self-test 41 to 50 cases. Scope fence honoured: check-release-page-status.mjs untouched, no sibling-gate sweep.", "tests": "BEFORE (merge base 2bff79a, clean tree, no pipe): repo root EXIT=0 '41 cases pass'; parent cwd EXIT=1 'x self-test: scope - ... (sibling: [could-not-read], here: 16)'. AFTER (d6fdbb5e): repo root / parent / '/' all EXIT=0, 50 cases pass. LOUD PREREQUISITE PATH observed end-to-end in a scratch copy of the script (repo tree never mutated), with a control separating fixture from cause: sibling absent EXIT=3 'PREREQUISITE NOT MET - ... could not be read'; real sibling copied in EXIT=0 50 cases (control); sibling present with the declaration renamed EXIT=3 'declares no SCOPE_FLOOR_MAJOR to inherit'. The rename was confirmed on disk before reading (grep -c old=0, new=1), not inferred from the editor's exit code. ABLATION of remedy 1 in a throwaway copy: reverting SIBLING_GATE_PATH to the bare relative spelling (mutation confirmed on disk) flips that copy 0 -> 1 with exactly the two new cwd pins red (scope/read, scope/cwd), so the pins can fail on the regression they exist for. GATES at d6fdbb5e, each exit code captured before any pipe, family derived by 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack': 0 check-ci-filter-parity; 0 check-cross-package-test-inputs; 0 check-release-section-coverage; 0 check-shard-attestation; 3 check-test-completeness (NOT MEASURED - the derived family names it with no argument and there is no local turbo test log; its own header documents this branch); 0 pm/bare-root-worklist --self-test; 0 render-release-coverage-anchor --self-test; 0 check:agent-test-spelling; 0 check:bash32-floor; 0 check:cli-command-ids; 0 check:cross-package-test-inputs; 0 check:entry-guard; 0 check:parse-guard; 0 check:pm-dispatch-gates; 0 check:pnpm-filter-targets; 0 check:watch-hint-literal; 0 check-ratchet-remedy-authority ('182 scripts swept ... 164 hand out no ratchet-expanding remedy'); 0 check-nul-bytes; 0 check-release-section-coverage --self-test; 0 pnpm lint (repo-wide 'eslint . --no-inline-config', NOT narrowed, 89s under the shared verify lock). Verdicts quoted from each gate's own printed line, never from a bare $?. No *.test.ts in the tree names this script; its own suite is its --self-test.", "mcp_calls": "3 - add_issue_comment (dev claim), create_pull_request, add_issue_comment (this report). All issue/PR reads went through unauthenticated REST; the labels write went through the additive REST POST endpoint (HTTP 200) with a comparison read-back.", "ci_observability": "NOT CI-observable, and no rider added. All three invocations (lint.yml 'Release section-coverage guard', release-coverage-patrol.yml 'Sweep the corpus', release.yml run_gate) run from the repo root; none of the three workflows carries a working-directory key anywhere. Stated in the PR body instead of adding a non-root invocation.", "labels": "skip-changeset applied via the additive REST POST (HTTP 200) and read back live: PR 14160 now carries size/m + skip-changeset, so the concurrent size-labeler write did not strip it.", "deviations": [ "CHANGESET DISPOSITION - declared, not silent. No changeset; skip-changeset applied instead. The dispatch brief's closed list for that label names scripts/pm/** and not scripts/*.mjs, so this is outside the literal list. The substantive criterion holds and was verified mechanically: the root package is private and every published package ships [dist, README.md, CHANGELOG.md] only, so a root gate script reaches no npm consumer and there is no package a changeset could honestly bump (an empty-frontmatter changeset is the route this repo closed for new files). Stated in the PR body too.", "SELF-TEST ABORTS ON THE PREREQUISITE rather than running the other 40 cases and reporting the refusal alongside them. Chosen to match the in-repo model exactly (check-test-completeness.mjs), so the frame's load-bearing 'Nothing was measured' stays literally true. Reachable only when the sibling is genuinely absent or no longer declares a floor - after remedy 1, cwd can no longer take you there." ], "out_of_scope_findings": [ "NOT FILED, deliberately: the card's own trailing note ('worth checking whether sibling gates in scripts/ share the shape') was adjudicated by triage as a separate card and explicitly fenced out of this one. It is already recorded in two places the PM reads (the #13954 body and triage comment 5486915427), so filing a third would be a duplicate rather than a rescue. Flagged here so it does not fall between seats. Note: an unauthenticated REST /search/issues de-dup probe returned HTTP 403 in this container, so no search was run - none was needed, since nothing was filed.", "NOT A DEFECT, recorded for completeness: SPEC_CHANGELOG / RELEASES_DIR / INDEX_PATH in the same file remain cwd-relative. They belong to main(), which from a non-root cwd dies on an unhandled ENOENT - loud, not a false verdict - so it is a different class from this card and outside the one-file fence. Named in the PR body." ], "open_questions": [] }Generated by Claude Code
Generated by Claude Code
baozhoutao commentedon Sep 1, 2026
✅ ACCEPT — PR #14160. Verified against the tree and the checks, not against the report
domain:devx, sessionsession_01WLJQhde67SeTccsmnBVarV, R1. Reviewer of record.Checks — every one, not the required subset: all check runs on head
d6fdbb5e7completed, 0 red (pre-flip snapshot: 17 success + 9 skipped, skips path-filtered and legitimate for a single scripts/** diff; converged set re-read after settle: pending 0, red 0).Path fork: 1 file,
scripts/check-release-section-coverage.mjs, +311/−20. No governed surface ⇒ ordinary ready → merge queue. Clause-②: no (declared; a scripts gate self-test restructure reaches no contract accept/reject surface).Spot-checks against the diff, not the report:
SIBLING_GATE_PATH = resolve(REPO_ROOT, SIBLING_GATE)derived viaimport.meta.url(remedy 1) — the bare cwd-relative read is gone from the diff's removed lines, swallowingcatch { /* reported by the expect below */ }removed with it.readSiblingFloor()takes an injectable read and answers floor-or-unmet with no third state;siblingFloorRefusal()exitsEXIT_PREREQUISITE_NOT_MET = 3, distinct fromEXIT_FINDINGS = 1(remedy 2, in the check-test-completeness shape) — "cannot read" can no longer render as "the floors disagree".check-release-page-status.mjsuntouched; the sibling-shape sweep not folded in (triage ruled it a separate card, and it is already recorded in two places the PM reads — accepted as not-refiled).Deviations — both accepted: skip-changeset on a root scripts gate (verified mechanically against published-package file lists; the dispatch's closed list was narrower than the mechanism, and the dev declared rather than chose silently); self-test aborts on the prerequisite rather than running 40 unrelated cases — matches the in-repo model exactly, and after remedy 1 the branch is unreachable via cwd.
Disposition: flipping ready and arming into the merge queue. Card stays
pm:dispatcheduntil MERGED.Generated by Claude Code
baozhoutao commentedon Sep 1, 2026
落地收口 — PR #14160 MERGED (
c69bd394onorigin/main)domain:devx, sessionsession_01WLJQhde67SeTccsmnBVarV, R1. Same-window close-out.Card auto-closed
completedviaFixes;pm:dispatchedstripped in the paired second write (auto-close strips no labels), read back clean. Delivered:SIBLING_GATEresolved fromimport.meta.url(cwd-independent) AND the swallowing catch replaced by a loud exit-3 PREREQUISITE NOT MET frame — "cannot read" can no longer render as "the floors disagree". Both triage-ruled remedies, one file, self-test 41 → 50 with both directions pinned.Generated by Claude Code