Repository navigation
[finding] cli-build-prerequisite exports CLI and CLI_PKG as CWD-relative constants, so its probe defers on every off-root run of either gate that uses it #11394
Description
Activity
Triage first-touch (Routine seat, hourly round): promoted
finding→pm:queue+domain:devx(+tooling), type Task.Rationale: the deferral direction is deliberate, but the cause is a plain path bug in a shared module (
scripts/cli-build-prerequisite.mjsCWD-relative constants), with the fix already named and precedented one file over (#10907: derive root fromimport.meta.url). Lands on both consumer gates at once — which is why #10907 correctly did not take it and why it is now its own card. Restores an invariant; no ruling needed.
Generated by Claude Code
Claim:
domain:devxPM seat, sessionsession_015ahemw8RcTgqtxrj15PEZx, branchclaude/issue-11394-cli-prerequisite-root-anchor.pm:queue→pm:dispatched. Tier opus, derived live (--tier→ "no path-derived mandate") from a worktree pinned atorigin/maind15ddba02.⛔ Zone 1
-
Branch from
origin/main, never baremain:
git fetch origin main && git worktree add /home/user/objectstack-issue-11394 -b claude/issue-11394-cli-prerequisite-root-anchor origin/main && cd /home/user/objectstack-issue-11394 && pnpm install -
Anchor the constants at the repo root, the way [finding] check:i18n-coverage resolves its population CWD-relatively, so an off-root run reports OK over a scan of nothing #10907 did for
check-i18n-coverage. That card's fix putREPO_ROOT = resolve(HERE, '..')behind one seam (at()), with the vocabulary staying repo-relative while every read is anchored. Same shape here:CLI/CLI_PKGmust resolve off the module's own location, notprocess.cwd(). -
⛔ The blocker that held this card is gone — confirm it, then proceed. check-i18n-coverage groups failure causes on a key containing the per-config rerun command, so one shared cause reports as N causes #11395's dev deliberately did not fix this, because doing so would have removed the environment state under which check-i18n-coverage groups failure causes on a key containing the per-config rerun command, so one shared cause reports as N causes #11395 was observable. check-i18n-coverage groups failure causes on a key containing the per-config rerun command, so one shared cause reports as N causes #11395 landed at
a1508766. So the reason to defer has discharged. Re-verify that on your base rather than taking my word:groupFailuresByCausekeys on the conclusion alone andrerunCommandis rendered per config, so the twelve-way split can no longer happen regardless of whether the probe defers. -
⛔ Never
git stash; ⛔ neverpkill -f; ⛔ never force-push or pushmain. Ablations undertrap restore EXIT INT TERM.
⚠️ Zone 2 — falsify, don't inherit- ⭐ This defect is an instrument, and things depend on it being broken. check-i18n-coverage groups failure causes on a key containing the per-config rerun command, so one shared cause reports as N causes #11395's repro used the deferring probe to reach the fallback branch. Before fixing, grep for anything else that relies on
resolveCliCommandFiledeferring off-root — a test, a fixture, a gate's own self-test. If something does, say so; silently removing a state other tests stand on is how a green suite starts measuring nothing. - The card is from 08-23 and its measurements predate check-i18n-coverage groups failure causes on a key containing the per-config rerun command, so one shared cause reports as N causes #11395 (
a1508766) and fix(scripts): key an i18n-coverage cause on its conclusion, not the per-config rerun command #11547. Re-derive: does the probe still defer off-root today, and doescheck:i18n-coveragestill printbuild prerequisite not pre-checkedfrom a foreign cwd? ⚠️ A zero from this probe is exactly the shape this repo keeps getting burned by —exit 1from an unmet prerequisite reads identically toexit 1from a real finding ([finding] two derived gates die with a raw ERR_MODULE_NOT_FOUND stack trace in a fresh worktree instead of reporting an unmet prerequisite #11557, which fired twice today). If your fix changes what the probe reports, make the unmet-prerequisite case say so in words, not only in an exit code.
Bar for done
- Non-vacuity, both directions: run the gate from a foreign cwd (e.g.
/tmp) before and after; before it must defer, after it must resolve. Mutation proven on disk in both directions. - Show the anchored read works from both the repo root and off-root — one direction alone proves nothing about a cwd-relative bug.
- ⛔ Do not change the vocabulary: the paths that are repo-relative on purpose (baseline keys, error-message text, rerun commands) must stay repo-relative. [finding] check:i18n-coverage resolves its population CWD-relatively, so an off-root run reports OK over a scan of nothing #10907's note says making them absolute would silently re-key twelve baseline entries.
- Run the derived gate union. Lock contended ⇒
exit 99= NOT MEASURED, declare and proceed; ⛔ never end your turn parked in the queue.
Open a draft PR with
Fixes #11394. Return the structured JSON report.
Generated by Claude Code
-
{ "issue": 11394, "status": "done", "branch": "claude/issue-11394-cli-prerequisite-root-anchor", "pr": "https://github.com/objectstack-ai/objectstack/pull/11650", "premise_still_valid": true, "summary": "Re-derived the card's premise on base 3637731e2 before writing anything: from /tmp the probe still returned {unknown: 'could not read packages/cli/package.json (ENOENT ...)'} while the root run returned {file:'packages/cli/dist/commands/lint.js'}, and check-i18n-coverage still printed the 'build prerequisite not pre-checked' line off-root. Fixed in scripts/cli-build-prerequisite.mjs by deriving REPO_ROOT from import.meta.url and putting one exported seam (atRepoRoot) between the vocabulary and the filesystem — the #10907 shape. Vocabulary untouched: CLI, CLI_PKG and oclifCommandFileFor's returned path stay repo-relative (pinned by a self-test assertion), only the read is anchored. One companion line was mandatory rather than optional: check-i18n-bundles.mjs asked existsSync(resolved.file) UNANCHORED, so anchoring only the read would have been strictly worse than the defect — the probe resolves off-root, the bare existsSync returns false, and the gate reports 'PREREQUISITE NOT MET — the workspace CLI is not built' naming a file that exists (measured, not predicted; LEG B below). The correct shape was already declared in the sibling: check-i18n-coverage.mjs carries existsSync(at(resolved.file)) with a comment naming that exact failure. The instrument question is discharged and verified rather than assumed: groupFailuresByCause keys on [reason, fix] with fix drawn from the closed SHARED_REMEDIES constant set and rerunCommand rendered per config by the report, and the origin/main off-root run confirmed it live — 12 failing configs reported '1 distinct cause', not twelve. Nothing else depends on the deferral (resolveCliCommandFile has exactly two callers; both self-tests reach the unknown branch through the pure oclifCommandFileFor({oclif:{}}, ...), untouched). On #11557: this adds no new silent exit 1 — it makes the WORDED prerequisite path reachable off-root, replacing a deferral line plus twelve CLI spawns with one verdict that already says 'Nothing was measured ... says NOTHING about whether any declared label went untranslated' and warns that piping hides the status.", "tests": "All exit codes captured before any pipe (redirect-then-capture); gate results quoted from each gate's own verdict line. FULL derived union run at final commit 7a390dfb0 — no narrowing declared. Union derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (script read the change set from git itself; provenance line confirmed 'objectstack-ai/objectstack at commit 7a390dfb0'; --repo assertion held). check:i18n-coverage self-test 0 + corpus 'OK (12 config(s), 657 baselined untranslated string(s), none new).' exit 0; check:i18n self-test 0 + corpus 'OK (9 package(s) — all bundles in sync, no undeclared authoring keys).' exit 0 (VERDICT command-exit 0); check:entry-guard self-test 0 / run 0 ('142 scripts/ file(s)' — the new export is inert on import); check:parse-guard 0/0; check:cross-package-test-inputs 0/0 ('All 116 self-test cases passed'); check:pnpm-filter-targets all three legs 0; check-ci-filter-parity.mjs 0; check-cross-package-test-inputs.mjs 0; check:nul-bytes 0 ('scanned 6506 text file(s) ... no raw ASCII control bytes') plus a manual grep -naP control-byte sweep of the three edited files (no hits). NON-VACUITY BOTH DIRECTIONS on ONE fully built tree (pnpm install + turbo build 72/72): check-i18n-coverage from the repo root and from /tmp produced BYTE-IDENTICAL output — verified by diff -q, not by eye — both exit 0, deferral-line count off-root 0. BEFORE, measured on a second detached worktree at origin/main 3637731e2 from /tmp: the deferral line present (count 1) followed by 12 CLI spawns. ABLATION, both legs under trap '<restore>' EXIT INT TERM: (1) reverting only the anchor to `atRepoRoot = (rel) => join(rel)` reds the new assertions — coverage self-test 1 failure, bundles self-test 2, each naming the cwd-relative read; (2) the same ablation on the fully BUILT tree from /tmp brings the deferral line back verbatim (count 1), proving the green 'after' column is not an unrelated tree. (3) LEG B, the call-site experiment on a simulated built tree (stub dist command files, packages/*/dist confirmed gitignored): with the probe anchored but existsSync left bare, the bundles gate from /tmp printed 'PREREQUISITE NOT MET — the workspace CLI is not built ... packages/cli/dist/commands/i18n/extract.js' about a file that existed — the #5862 defect my companion line prevents; with both anchored it passed the prerequisite. Every mutation was proven on disk by BOTH marker counts (old=1->0, new=0->1) and a sha256 change, never by an editor exit code; every restore was verified byte-identical by sha (cli-build-prerequisite.mjs back to 077e3349d942abe9..., bundles back to 28374f047bdc4025...), and git status was empty with the stub dist gone. These scripts are plain .mjs read from source, so no dist rebuild is in the resolution path — no build leg applies to the ablation. NOT MEASURED: nothing; the corpus halves initially failed on unbuilt workspace packages (connector-mcp, then connector-openapi), so I built the full workspace and re-ran them green. One earlier build attempt was killed by the ~10-minute foreground cap (exit 143); the lock was released correctly (another agent's holder appeared immediately after) and the retry finished in 2m12s on a warm turbo cache. Heavy work went through scripts/pm/os-verify-lock.sh throughout (one legitimate 4m21s queue wait behind another agent's build).", "open_questions": [], "out_of_scope_findings": [ "filed as #11647: check-i18n-bundles resolves its own POPULATION CWD-relatively (`findExtractConfigs('packages','packages')`) and dies with an uncaught ENOENT scandir stack from any foreign cwd — verified pre-existing at origin/main 3637731e2 (same stack, there preceded by the deferral line), so this PR neither introduces nor fixes it. It is the last unanchored half of the pair #10907 fixed on the coverage side. Searched first (no duplicate; #10907 is the closed coverage equivalent). Filed unassigned, no labels, no pm:queue — it is a concrete defect left for PM triage, not an observation." ] }
Generated by Claude Code
Landed — PR #11650, verified by content on
origin/main781cc0b52 fix(scripts): anchor the shared CLI-build probe at the repo root (#11650) scripts/cli-build-prerequisite.mjs atRepoRoot 0 -> 3 import.meta.url 0 -> 1 CTRL CLI_PKG 5 -> 6 CTRL oclifCommandFileFor 3 -> 4 scripts/check-i18n-bundles.mjs:736 existsSync(atRepoRoot(resolved.file))Card closed
completed,closed_by_pull_requestsrecords #11650.pm:dispatchedstripped.⭐ The naive fix would have been strictly worse than this defect
This report described a probe that defers off-root — honest, if noisy. Anchoring only that probe would have turned the deferral into a false diagnosis:
check-i18n-bundles.mjsaskedexistsSync(resolved.file)unanchored, so with the probe resolving correctly the bare check goes false and the gate announcescheck-i18n-bundles: PREREQUISITE NOT MET — the workspace CLI is not built packages/cli/dist/commands/i18n/extract.js…about a file that exists. A deferral says nothing was measured; that says the wrong thing was measured. #5862's shape.
The dev did not reason this out and stop — LEG B produced that sentence on a simulated built tree, on a claim where predicting would have been easy and would have been believed. And the shape it adopted was already declared one file over (
check-i18n-coverage.mjs:1074carriesexistsSync(at(resolved.file))with a comment naming this precise failure), so the two siblings converge rather than drift again.The instrument question this card was holding open
#11395's dev deliberately left this defect standing, because the deferring probe was what made the twelve-way cause split observable. That reason was discharged by observation, not inference:
groupFailuresByCausekeys on[reason, fix]withfixfrom the closedSHARED_REMEDIESset, and theorigin/mainoff-root run reports1 distinct causefor 12 failing configs — not twelve.Running the old tree and watching it report
1 distinct causeis an observation; "my blocker landed, so I'm unblocked" is an inference about someone else's work. This lane got burned three times today by the second kind.Vocabulary vs. reads
CLI,CLI_PKGandoclifCommandFileFor's returned path stay repo-relative — baseline keys, error text, rerun commands, and an argv path spawned against a repo-root cwd — with only the reads anchored, pinned by a self-test assertion. "Just resolve everything from root" would have silently rewritten baseline keys and rerun commands.Follow-on now unblocked
#11647 — the same file's population (
findExtractConfigs('packages','packages')) is still CWD-relative and dies with an uncaught ENOENT from any foreign cwd. Correctly left out of this PR: that is the population, this was the probe, and folding them would have made the before/after unreadable. ItsBlocked-by: #11394has now discharged and it is back in the queue.
Generated by Claude Code
Observation-class, measured while fixing #10907. No gate is red — CI invokes both consumers from the repo root. Filed unassigned, no
pm:queue.The shape
scripts/cli-build-prerequisite.mjsis the one shared answer to "is the workspace CLI built?", used byscripts/check-i18n-coverage.mjsandscripts/check-i18n-bundles.mjs. Two of its exported constants are CWD-relative:resolveCliCommandFile()readsjoin(CLI_PKG, 'package.json')off those, so from any cwd but the repo root it ENOENTs and returns anunknownreason rather than a derived path.Measured residue
#10907 anchored
check-i18n-coverage.mjsto a module-derived root. Its off-root run is now correct — same verdict as the root run — but the probe above still cannot pre-check, so the run is preceded by a line that reads like a problem when nothing is wrong:And on an unbuilt tree the deferral costs the diagnosis outright. From the root the reader gets one verdict naming the one cause; from off-root the probe defers, every config reaches the in-loop path instead, and the same single environment fact is reported as twelve failures.
Why it is worth recording
The deferral direction is deliberate and correct —
resolveCliCommandFile's docblock argues that a probe which cannot read the declaration must not turn a correctly-built workspace red — so this is not a request to change that. What is worth recording is that the reason it cannot read the declaration is a fixable path bug rather than a genuine unreadable shape, and the fix is the same one #10907 applied one file over: derive a root fromimport.meta.urland join against it. Because the module is shared, the fix lands on both consumer gates at once, which is also why #10907 deliberately did not take it — its ruled file surface wascheck-i18n-coverage.mjsalone, and widening it would have moved a second gate's behaviour under a card that had not measured it.Refs
#10907 (where this was measured; that card anchored the consumer, not this shared module)
Generated by Claude Code