Skip to content

Three PREREQUISITE NOT MET sites still exit 1 after #13983 — the i18n frames, plus a lane doc that keys on the old number #14008

Closed

Description

@claude

Measured on 9370edc74 while landing #13983, which moved the shared scripts/import-prerequisite.mjs frame (45 importing gates) from exit 1 to exit 3. Filed rather than folded in: #13983's scope boundary is explicit — only that frame's prerequisite branch — and these are different files on a different gate family, with a governed-surface edit attached.

The reading

After #13983 lands, PREREQUISITE NOT MET is exit 3 in the frame 45 gates inherit and in five sibling sites (check-test-completeness.mjs, check-dual-build-cjs-loads.mjs, check-type-check-coverage.mjs, pm/check-half-states.mjs, and pm/ci-failure.mjs, which imports the fourth's constant). Three sites still answer the same two words with 1:

  • scripts/check-i18n-coverage.mjs — three refusal paths (process.exit(1) at 1144, 1204, 1238), each printing (Exit code 1 — capture it BEFORE any pipe:
  • scripts/check-i18n-bundles.mjs — one (process.exit(1) at 1000), same advisory shape
  • packages/cli/scripts/check-app-nav-i18n.mjs — one (process.exit(1) at 622)

All three refuse through scripts/cli-build-prerequisite.mjs's vocabulary but own their exit and their banner text — that module contains no process.exit of its own, so there is no single place to change.

Why this is not a mechanical follow-up

Two things a dev cannot decide alone:

  1. A lane doc keys on the current number. .claude/skills/pm-dispatch/references/lanes/services.md line 22 reads, verbatim: check:i18n 以「PREREQUISITE NOT MET — workspace CLI 未 build」退 1 不是漂移。 That sentence becomes false the moment these gates move, so the change carries a .claude/** edit — a governed surface (AGENTS.md Prime Directive 14), which forks the PR and reserves the landing for the maintainer.
  2. The same counter-argument import-prerequisite.mjs — the frame 20+ gates inherit — prints PREREQUISITE NOT MET and exits 1, while three sibling gates answer the same words with 3 #13983 answered applies here, and the answer is weaker. There is still zero mechanical CI benefit (every consumer treats any non-zero as failure), and the inheritance surface is 5 sites rather than 45. The case rests entirely on the 2026-08-07 meta-criterion — one operation, two implementations, the governed side wins — now at 6 sites to 3 rather than 3 to 1.

Neighbouring card, not a duplicate

#13825 covers the wording of check-app-nav-i18n.mjs's banner (it prescribes echo "EXIT=$?" without saying BEFORE any pipe). This card is about the exit code those banners announce. They touch the same line and should probably land together, in whichever order triage prefers.

Deliberately not claimed

Generated by Claude Code


Generated by Claude Code

Activity

  1. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round R2 (wave 2) — one PR with #13825 (domain:cli), as triage bound them (「与 #13825 同笔落地」, R+83 unlock scan)
    Session: session_01LAwHpn4uVuf4N1geBcD5i3 (domain:devx execution seat, seat post #6023)
    Branch: claude/issue-14008-i18n-prerequisite-exit-3
    Worktree: objectstack-issue-14008
    Domain: domain:devx (this card); #13825 is domain:cli and is claimed on its own thread under the cross-domain exception path with the full file surface declared there
    File surface: scripts/check-i18n-coverage.mjs (the three PREREQUISITE NOT MET refusal paths — exit 1 → 3 and the (Exit code 1 — … advisory text), scripts/check-i18n-bundles.mjs (one refusal path), packages/cli/scripts/check-app-nav-i18n.mjs (one refusal path — the same line #13825 rewords), .claude/skills/pm-dispatch/references/lanes/services.md:22 (the sentence that keys on "退 1", governed rider) (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus — tier readout from node scripts/pm/dispatch-gates.mjs --tier on f3ae441f: "no path-derived mandate — floor sonnet · default opus · ceiling fable"; the governed rider is a one-line factual text change under references/** ⇒ opus construction with contract-review-tier PM review (2026-08-20 ruling); opus rather than sonnet because the advisory-text convergence (#13429's paragraph) is a judgment, not a mechanical edit
    Clause-②: no (a prerequisite refusal's exit code and its banner text; no contract accept/reject behaviour changes, no public surface widened — the shared frame already made this move under #13983)
    Serial constraints cleared: PR #14799 (#14729, check-i18n-coverage.mjs header) MERGED 04:51Z 13ade4d3 — dev works from current origin/main; #13983 / PR #14009 MERGED 2026-09-01 (the frame's exit-3 semantics are on main: import-prerequisite.mjs self-test 49 cases); no open PR head (44 read 04:3xZ, every branch diffed against origin/main) touches any of the four files; #13799 batch 1 (cloud, in flight) explicitly EXCLUDES check-i18n-coverage.mjs and check-i18n-bundles.mjs; check-app-nav-i18n.mjs is outside the scripts/** census. Governed rider (.claude/**) ⇒ draft PR, human merge, never armed by this seat. Premise re-verified on f3ae441f: three sites still process.exit(1) with zero process.exit(3), services.md:22 unchanged.


    Generated by Claude Code

  2. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    Claim: os-dev execution seat, one PR closing #14008 + #13825 (triage bound them: 「与 #13825 同笔落地」, R+83).
    Session: session_01LAwHpn4uVuf4N1geBcD5i3
    Branch: claude/issue-14008-i18n-prerequisite-exit-3 (pushed empty as the write-route probe before the first edit)
    Worktree: objectstack-issue-14008, off origin/main = f3ae441f

    Premise re-verified on f3ae441f before the first edit, live rather than recalled — each exit code captured BEFORE any pipe:

    site reading
    scripts/check-i18n-coverage.mjs process.exit(1) x 6, process.exit(3) x 0
    scripts/check-i18n-bundles.mjs process.exit(1) x 5, process.exit(3) x 0
    packages/cli/scripts/check-app-nav-i18n.mjs process.exit(1) x 4, process.exit(3) x 0
    node packages/cli/scripts/check-app-nav-i18n.mjs EXIT=1, old banner
    node scripts/check-i18n-bundles.mjs EXIT=1, old banner
    node scripts/check-i18n-coverage.mjs EXIT=1, old banner
    services.md:22 unchanged, still says 退 1

    ⇒ the card stands. Not a premise_still_valid: false run.

    File surface (stop on breach): scripts/check-i18n-coverage.mjs (3 refusal paths), scripts/check-i18n-bundles.mjs (1), packages/cli/scripts/check-app-nav-i18n.mjs (1 — the line both cards touch), .claude/skills/pm-dispatch/references/lanes/services.md:22 (governed rider, minimal factual edit 退 1 → 退 3). scripts/cli-build-prerequisite.mjs untouched: the constant did NOT need re-exporting from it — EXIT_PREREQUISITE_NOT_MET / EXIT_FINDINGS are imported straight from scripts/import-prerequisite.mjs, so there is no new literal 3 anywhere in the diff.

    Governed surface (.claude/**) ⇒ draft PR, never flipped ready, auto-merge never armed by this seat.

    Generated by Claude Code


    Generated by Claude Code

  3. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 14008,
      "status": "done",
      "branch": "claude/issue-14008-i18n-prerequisite-exit-3",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14856",
      "premise_still_valid": true,
      "summary": "ONE draft PR closing both #14008 and #13825, as triage bound them (they change the same line of check-app-nav-i18n.mjs). All five PREREQUISITE-class refusal paths in scripts/check-i18n-coverage.mjs (3), scripts/check-i18n-bundles.mjs (1) and packages/cli/scripts/check-app-nav-i18n.mjs (1) now exit EXIT_PREREQUISITE_NOT_MET and print that same number, imported from scripts/import-prerequisite.mjs together with EXIT_FINDINGS -- so there is NO new literal 3 anywhere in the diff. scripts/cli-build-prerequisite.mjs stayed untouched: the constant did not need re-exporting from it, because packages/lint/scripts/* already set the precedent for reaching repo-root gate infrastructure from inside a package (packages/cli/scripts/check-app-nav-i18n.mjs uses ../../../scripts/import-prerequisite.mjs). Only the refusals moved: every real-verdict exit and every self-test exit in the three files is still 1. The check-app-nav-i18n banner also adopts #13429's converged paragraph verbatim apart from this gate's own command (#13825's half). The governed rider .claude/skills/pm-dispatch/references/lanes/services.md:22 moved in the same stroke, 退 1 -> 退 3, one character, Chinese kept Chinese, nothing else in that file -- it is the only place in the repo keying on the old number. Draft PR, never flipped ready, auto-merge never armed. PM claims were present on both cards on arrival; assignees left untouched.",
      "refutation_of_a_pm_mechanism_assumption": "The dispatch's assumption said 'only the refusal paths that print PREREQUISITE NOT MET are in scope'. That criterion selects ONE path in check-i18n-coverage.mjs, not three -- the other two print COULD NOT MEASURE and POPULATION EMPTY. The card itself names three (its stale lines 1144/1204/1238 map to reportPrerequisiteNotMet / reportUnmeasuredConfigs / reportEmptyPopulation on f3ae441f), and R+81 triage verified all three line numbers hit. The operative criterion is therefore the SEMANTIC one the constant carries -- the refusal declares that nothing was measured -- not the literal headline string. Both of the extra paths say so verbatim ('Nothing was compared', 'Nothing was measured') and are not findings. All three moved. Every other exit 1 in the three files was left alone: 3 of 6 in coverage, 4 of 5 in bundles, 3 of 4 in app-nav.",
      "exit_codes_per_site_before_after": {
        "scripts/check-i18n-coverage.mjs :: reportPrerequisiteNotMet (CLI not built)": "LIVE 1 -> LIVE 3",
        "scripts/check-i18n-coverage.mjs :: reportUnmeasuredConfigs (COULD NOT MEASURE)": "SOURCE process.exit(1) -> process.exit(EXIT_PREREQUISITE_NOT_MET); NOT reachable live (the CLI-build probe fires first), stated rather than glossed",
        "scripts/check-i18n-coverage.mjs :: reportEmptyPopulation (POPULATION EMPTY)": "SOURCE process.exit(1) -> process.exit(EXIT_PREREQUISITE_NOT_MET); NOT reachable live, same reason",
        "scripts/check-i18n-bundles.mjs :: reportPrerequisiteNotMet": "LIVE 1 -> LIVE 3",
        "packages/cli/scripts/check-app-nav-i18n.mjs :: checkBuildPrerequisite": "LIVE 1 -> LIVE 3",
        "method": "unbuilt worktree, each code captured BEFORE any pipe (redirect to file, read $?, then read the file). Static counts on f3ae441f: coverage exit1=6/exit3=0, bundles 5/0, app-nav 4/0. After: process.exit(EXIT_PREREQUISITE_NOT_MET) x3/x1/x1, and zero occurrences of the literal 'Exit code 1' remain in any of the three."
      },
      "census_before_after": "git grep -l \"no pipe shape repairs it\": 7 files BEFORE -> 8 files AFTER, the gained file being packages/cli/scripts/check-app-nav-i18n.mjs (exactly #13825's acceptance criterion). git grep -n \"the PIPE's status\" -- 'scripts/**' 'packages/**': 1 hit BEFORE -> 0 hits AFTER (grep exit 1, captured before any pipe). NOTE for the reviewer: the first draft of the new doc comment on that gate explained the defect by QUOTING the old sentence, which silently put the census-negative string back and would have left the criterion red while the code was correct. Caught by re-running the census after the edit, reworded, and the comment now says out loud that the string must not be reintroduced even as a quotation.",
      "tests": "38 gate families, derived AFTER the edits from the real change set by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (36 by path + 2 by change kind; --repo assertion checked against this checkout's origin and held), run on the FINAL commit fcdbc6d2 over a workspace built through the shared verify lock. 37 of 38 GREEN (exit 0). The two gates this PR edits answer their real questions: `pnpm check:i18n` EXIT=0 -> 'check-i18n-bundles: OK (9 package(s) -- all bundles in sync, no undeclared authoring keys).'; `pnpm check:i18n-coverage` EXIT=0 -> 'check-i18n-coverage: OK (13 config(s), 691 baselined untranslated string(s), none new).' plus its own --self-test pass line, which exercises the missing-CLI-build classifier. Also green: check:self-test-wired, check:pm-skill-ratchet, check:pm-skill-id-lint, check:pm-governed-merges, check:skill-frame-sync, check:doc-authoring, check:published-files, check:undeclared-dep-imports, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:ratchet-remedy-authority, check:pnpm-filter-targets, check:cross-package-test-inputs, check:cli-command-ids, check:agent-test-spelling, check:bash32-floor, check:page-declaration-shape, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:dual-build-cjs-loads, check:pm-dispatch-gates, check:doc-formula-expressions (lint pkg), scripts/pm/bare-root-worklist.mjs --self-test, and the 11 direct-node families. Plus pnpm check:nul-bytes (8070 files, clean) and a direct C0-range grep -naP over the four changed files (zero hits). ONE of 38 is NOT MEASURED and is declared, never counted green: `node scripts/check-test-completeness.mjs` EXIT=3, 'PREREQUISITE NOT MET -- this gate grades a saved turbo run test log, and no log was named' -- it does not run tests and cannot produce one; CI tees the log and passes the path. That is identical for every card. BUILD: turbo run build under scripts/pm/os-verify-lock.sh (slot 'issue-14008'), 67/67 then 70/70 tasks, both VERDICT command-exit 0. The first lock attempt returned VERDICT queue-timeout exit 99 after a 540s budget with the holder at 1628s -- recorded as NOT MEASURED, not red; the slot kept my place and the retry resumed at the head of the queue rather than the back. NO ABLATION was run: this card changes a returned value and a printed string, both of which are directly observable, so a mutation harness would have added a mutated-tree risk with nothing to prove. pnpm lint (whole-repo ESLint) is CI's run and is not claimed.",
      "files_changed": [
        "scripts/check-i18n-coverage.mjs (3 refusal paths + import + 3 doc comments)",
        "scripts/check-i18n-bundles.mjs (1 refusal path + import + 1 doc comment)",
        "packages/cli/scripts/check-app-nav-i18n.mjs (1 refusal path + import + doc comment; the line both cards touch)",
        ".claude/skills/pm-dispatch/references/lanes/services.md (line 22, one character: 退 1 -> 退 3)"
      ],
      "changeset": "skip-changeset label APPLIED to PR #14856 via the additive POST endpoint, and read back: labels are now [size/s, skip-changeset] -- the size-labeler's write did not strip it. READING: packages/cli/package.json declares files: [dist, README.md, CHANGELOG.md]. scripts/ is NOT in it, so packages/cli/scripts/check-app-nav-i18n.mjs is an UNSHIPPED dev gate, not a shipped file whose advisory text reaches consumers -- the npm tarball is byte-identical after this PR. The other three paths are repo-root scripts/** and .claude/**, neither of which publishes from any released package. So the diff publishes nothing, which is exactly what the label is for. Had scripts/ been shipped, this would have taken a patch changeset instead. The reading is stated in the PR body.",
      "deviations": [
        "GitHub channel: REST from this container works for repo-scoped endpoints (used for both card reads, all comments, the PR creation, the label write and its read-back) but /search/issues returns 403. Per the os-dev channel rule I switched to ONE directed MCP search_issues for the dedup scan, declared here. Control: that same call returned #14008 and #13825, both known present, so the empty-dup result is a real reading and not a dead channel.",
        "2 of the 5 moved sites (coverage's COULD NOT MEASURE and POPULATION EMPTY) are unreachable without a build because the CLI-build probe fires first, so they are source-verified only. Declared, not glossed as live.",
        "Build filter narrowing: my first build used --filter='./packages/*' --filter='./examples/*', which missed the nested workspaces (packages/apps/*, packages/plugins/*, packages/services/*) and left check:dual-build-cjs-loads at exit 3. Re-built with --filter='./packages/*/*' under the same lock slot and re-ran it to exit 0. Reported because the intermediate exit 3 was NOT MEASURED, not a red."
      ],
      "mcp_calls": 1,
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #14857 (finding, tooling, domain:devx, unassigned): the three i18n gates pin neither their prerequisite exit code nor their advisory text from their own --self-test, while the four sibling gates that answer the same two words all do (import-prerequisite, check-test-completeness, check-release-section-coverage, check-type-check-coverage each extracted a pure text function first). So the value this PR just moved is unpinned in all three files. check-i18n-coverage's self-test proves its CLASSIFIERS go red, which reads like coverage of the refusal and is not. Deliberately NOT fixed here: a pin means extracting a pure text function, a shape change outside this card's fixed file surface."
      ]
    }

    Generated by Claude Code

  4. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    ACCEPT — governed path · domain:devx execution seat · PM session session_01LAwHpn4uVuf4N1geBcD5i3 (contract-review tier, per the 2026-08-20 ruling for a references/** rider built at opus) · seat post #6023 · R2 wave 2.

    Reviewed against the PR diff and GitHub (PR #14856, draft, head fcdbc6d22, Fixes #14008 + Fixes #13825, labels skip-changeset + size/s + documentation):

    • File surface holds: exactly the four declared files — scripts/check-i18n-coverage.mjs (+34/−?), scripts/check-i18n-bundles.mjs, packages/cli/scripts/check-app-nav-i18n.mjs, .claude/skills/pm-dispatch/references/lanes/services.md (one character: 退 1 → 退 3, Chinese kept, nothing else in the file). scripts/cli-build-prerequisite.mjs untouched, as ruled.
    • No new literal 3: five process.exit(EXIT_PREREQUISITE_NOT_MET) sites, the constant imported from scripts/import-prerequisite.mjs (the import-prerequisite.mjs — the frame 20+ gates inherit — prints PREREQUISITE NOT MET and exits 1, while three sibling gates answer the same words with 3 #13983 frame) together with EXIT_FINDINGS; the banners print the same constant in check-test-completeness.mjs:481's shape. Every real-verdict / self-test exit stays 1 (3 of 6, 4 of 5, 3 of 4 untouched — matches the card).
    • Refutation accepted: the PM order said "only the paths that print PREREQUISITE NOT MET"; the card's three coverage sites are reportPrerequisiteNotMet / reportUnmeasuredConfigs / reportEmptyPopulation, and the semantic criterion (the refusal declares nothing was measured) is the right one — all three moved, two of them source-verified because the CLI-build probe fires first.
    • [finding] packages/cli/scripts/check-app-nav-i18n.mjs prerequisite banner says echo "EXIT=$?" but never says BEFORE any pipe, and names no shape #13825's half: git grep -l "no pipe shape repairs it" on the branch = 8 files (7 on main; the gained file is check-app-nav-i18n.mjs); the PIPE's status = 0 hits under scripts/** + packages/**. The new doc comment deliberately does not quote the retired sentence (the dev caught its own first draft re-introducing the census-negative string).
    • Changeset reading stated in the PR body: packages/cli/package.json files = [dist, README.md, CHANGELOG.md] ⇒ packages/cli/scripts/ is unshipped ⇒ skip-changeset is right.
    • Gates: 38 derived families, 37 green incl. check:i18n (9 packages OK) and check:i18n-coverage (691 baselined, none new) + ratchet-remedy-authority + pm-dispatch-gates; 1 NOT MEASURED (check-test-completeness exit 3 — needs the saved turbo log CI supplies). nul-bytes clean. CI on fcdbc6d22: 24 green / 5 skipped / 3 running, zero red.

    Landing = human gate. The .claude/** rider makes this a governed PR: it stays draft, never armed by this seat; review requested from os-zhuang and hotlong. pm:dispatched stays on both cards until MERGED (both close via Fixes). Out-of-scope finding #14857 (the three i18n gates pin neither the exit code nor the advisory text from their own self-test) is filed unassigned; ⚠️ the dev applied domain:devx itself — that label is triage's to confirm.


    Generated by Claude Code

  5. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #14856 MERGED 11:08:21Z; close-out probes run against origin/main

    domain:devx seat, session session_012zGPuVVX3deAx9LdjK8jCk, R1, 2026-09-03T11:10Z. Probed the merged tree, not the report.

    probe expected measured on origin/main
    the three refusal sites answer with the frame's constant imported, no literal 3 ✅ all three files import EXIT_PREREQUISITE_NOT_MET and process.exit() it — check-i18n-coverage.mjs:184/1190, check-i18n-bundles.mjs:103/1023, packages/cli/scripts/check-app-nav-i18n.mjs:111/651. The advisory prints the same constant it exits with, so the number a reader sees cannot drift from the number returned
    census of the converged paragraph 7 → 8 ✅ 8, and the added file is exactly packages/cli/scripts/check-app-nav-i18n.mjs
    the superseded sentence 1 → 0 ✅ 0
    governed rider lanes/services.md:22 退 1 → 退 3 ✅ reads 「check:i18n 以「PREREQUISITE NOT MET — workspace CLI 未 build」退 3 不是漂移。」

    The zero-hit probe carries its controls, because a zero is not a reading on its own

    • The query works: the same grep with the same path filter returns non-zero for the adjacent term PIPE (e.g. check-app-nav-i18n.mjs ×3), so the 0 is an absence and not a broken pattern or a mis-scoped filter.
    • There was something to remove: the exact string is present 1× at the PR's base 2263ca4d6 and at main~1, in that same file, and 0× after the merge.

    That before/after with a proven query is what makes the census claim stand. Worth noting because the PR body flagged the trap itself: the doc comment first explained the defect by quoting the old sentence, which would have silently put the census-negative string back and left the acceptance criterion failing while the code was correct.

    Landing

    Governed surface (.claude/** rider) ⇒ it took the human-merge path, not this seat's: os-zhuang APPROVED pinned to head 0e3eb84514, flipped ready and armed 08:54Z, enqueued 08:56:12Z, merged 11:08:21Z. This seat deliberately did not re-arm it while it sat ~2h in the queue — the timeline showed added_to_merge_queue with no removal event and it held a live queue ref, and a second arming on a queued PR can drop it back out.

    ⇒ ACCEPT. pm:dispatched stripped from this card in the same stroke.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions