Skip to content

fix(cli): i18n extract --check --json compares instead of exiting 0 - #16672

Merged
os-sales merged 4 commits into
mainfrom
claude/issue-16600-i18n-extract-check-json
Sep 7, 2026
Merged

fix(cli): i18n extract --check --json compares instead of exiting 0#16672
os-sales merged 4 commits into
mainfrom
claude/issue-16600-i18n-extract-check-json

Conversation

@claude

@claude claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #16600

os i18n extract CONFIG --locales=zh-CN --no-metadata-forms --out=DIR --check --json exited 0 having compared nothing, on a tree whose bundles had provably drifted. The same invocation without --json exited 1 on the same fixture and printed Translation bundles have drifted from the schema — the card's own positive control, so the exit 0 was a reading and not "nothing to compare".

Re-verified on today's main before any repair

The card's dispatch asked for this because the --dry-run twin (#16480) landed as PR #16627 earlier today and moved the control flow of this same function. It did not incidentally repair this one. Measured at 0a61db1f5d (the branch point, today's origin/main), by checking the pre-repair source back out under the finished branch and driving the card's own two invocations:

invocation exit what it reported
… --out=OUT --check 1 missing: OUT/zh-CN.objects.generated.ts, then the drift sentence
… --out=OUT --check --json 0 the ordinary extract payload — no drift field, no comparison, no failure
… --check --json with no --out 0 the ordinary extract payload, having been asked for a comparison it could not make

if (flags.json) { await emitJson(...); return; } sat ahead of both the --check needs---out guard and the comparison block. --json is if anything the more likely CI spelling of the two, because a pipeline that wants to parse the result reaches for it.

What this changes

Under --json, --check is a verdict mode, so the comparison now runs before the one document the run is allowed to write. Four invocations, and only the first two move:

  • drift found — the run ends on this command's existing { "error": ... } envelope with exit 1, carrying the same sentence the console face prints, regenerate-and-commit command included.
  • --check with no --out — the refusal is now reachable on the machine face, in the same envelope, exit 1.
  • in sync — unchanged: the ordinary extract payload, exit 0.
  • --json without --check — unchanged in every respect.

The run leaves through exactly one of those faces, so stdout still parses as exactly one JSON document. Emitting the payload and then an error envelope would be the two-document defect isExitSignal records in utils/format.ts.

The file list and the comparison became one closure each — emittedFiles, compareCommitted, driftMessage — read by both faces, so the console and machine --check cannot disagree about what this run produces or end on different words.

⚠️ A pipeline that runs --check --json and was green may now go red, and that is this repair working. The green was a comparison that never happened; the red is drift that was already in the tree. The changeset says so in the words that ship to consumers.

Clause-②

Clause-②: no

⭐ Ruled at the contract-review tier on this PR, after the PM initially judged it yes; both carriers are cleared. The criterion is directional — does the diff relax the accept set or expand the public face — and neither happens here: nothing is newly accepted, two inputs that were silently accepted against the declared contract are now refused as --check's own help text says, and the --json stdout set was already {payload} ∪ {{error, ...}} and still is. The --dry-run twin, #16480 / PR #16627 — same command, same class, merged this morning — went Clause-②: no + patch on exactly this shape. ⛔ This is not a general rule that copying an envelope is never a widening; it is that on this diff the copying produced no payload shape.

The changeset is patch accordingly: a fix( that changes no public surface stays patch.

Why the payload is not widened, which is the question the declaration turns on. No drift / missing / stale member is added to any payload, and no member of the existing payload changes. This command has exactly one machine-readable failure envelope — { error, ...errorCodeFields }, compact, exit 1, in this same file's catch — and every other way the command can fail already speaks it, the --check needs---out refusal included, which is the same mode as the drift refusal and is now routed there too. Giving drift a different shape than the refusal beside it would have been the third spelling triage forbade. Naming the drifted files on the machine face is additive, can land later without breaking anyone, and stays the PM's to route as its own card. os lint --json and os i18n check --json report a verdict in the payload with the exit code derived from the same count; that is the shape such a widening would copy, and neither is foreclosed here.

Breaking-ness: NOT declared

No **BREAKING** banner and no ADR-0087 disposition, concurred at the at-tier review independently of the level. Nothing an author can write is removed, renamed, or narrowed: no spec key, no export, no config field, no type surface. There is no FROM → TO mapping to ship and no tombstone to write, and ADR-0087 is the metadata migration ledger — a CLI exit code enforcing its own documented semantics is not a migration. The flag's published description is Write nothing; fail if the committed bundles in --out differ from a fresh extract, which the old --json path contradicted and the new one honours. ⛔ No banner was added "to be safe", which would assert a migration prescription that does not exist, and none was omitted to dodge the gate, which is opt-in by declaration.

The remedy the machine face prints actually heals (review finding F1)

⭐ Caught at review, and it was a real blocker rather than a nit: rerunThatRegenerates (was rerunWithoutCheck) stripped only --check, so on the machine face the command the failure named kept --json — "output JSON instead of writing files". An operator or CI log reader who ran exactly what the failure printed got a payload, wrote zero files, and the next --check --json failed again with the same advice. That is the #14895 loop — "the failure is self-healable and the advice is what stops it healing" — reproduced on the very face this PR creates.

Now the helper drops --json and --json=... alongside --check, and the degraded fallback line names the same two tokens under --json so the built command and the fallback cannot prescribe different things. Driven end to end on the card's fixture:

$ os i18n extract CONFIG ... --out=OUT --check --json
{"error":"Translation bundles have drifted from the schema. Regenerate and commit:\n  os i18n extract CONFIG ... --out=OUT"}
-> exit 1
$ os i18n extract CONFIG ... --out=OUT            # the command it just named
-> exit 0, OUT now holds zh-CN.objects.generated.ts
$ os i18n extract CONFIG ... --out=OUT --check --json
-> exit 0

⚠️ The pin missed this because jsonVerdict read only the first line of a two-line envelope. That is the same partial-reading shape this card family is about, so the remedy is now pinned on its own two booleans — remedyNamesOut and remedyCarriesJson — asserted on every machine case.

How it was verified

All readings below are on head 3fd7a6b0a6, exit codes captured before any pipe.

  • Pin: packages/cli/test/i18n-extract-check-json.test.ts, 5 cases, integration project — Test Files 1 passed, Tests 5 passed, exit 0. Every case is the card's required two-invocation contrast — same fixture, same --out, differing only by --json — and none of them asserts the exit code alone: each drift case pins the reported drift and the remedy read out of the JSON document, plus a purity assertion that stdout parses as exactly one document, plus the filesystem staying untouched. Not named .e2e on purpose: this regression reads GREEN, so it has to run on pull requests and in the queue rather than nightly.
  • @objectstack/cli unit tier: Test Files 183 passed, Tests 2511 passed | 6 expected fail, exit 0. typecheck exit 0.
  • Ablation, leg A — the whole repair. The pre-repair source checked back out from the branch point, proven on disk by anchor counts and by git hash-object matching that commit's blob, then restored with git checkout HEAD -- ... and proven back by hash and a whole-tree git status --porcelain. Result: 3 failed / 2 passed — the two drift cases and the needs---out case go red; the in-sync case and the plain---json case stay green.
  • Ablation, leg B — the F1 half on its own. Keeping the repair and removing only the --json arm from the remedy helper: 2 failed / 3 passed, the two drift cases, on the remedyCarriesJson / remedyNamesOut assertions and nothing else. So the remedy pin discriminates by itself rather than riding on the exit-code cases.
  • ⚠️ The in-sync case is green on both sides of leg A, deliberately and by design — before the repair, --check --json on an in-sync tree also exited 0 with the payload. That is the [finding] os i18n extract --check --dry-run exits 0 having compared nothing — a --check that passes without checking, i.e. a false green in CI #16480 lesson restated in this card's terms: an exit-code-only suite would have stayed green over the very regression it was written for. Its job here is to stop "always fail under --check --json" from satisfying every other case in the file.
  • Lint: the repo-wide eslint . --no-inline-config is CI's; locally it is narrowed to the two .ts files this diff touches, 0 errors 0 warnings, exit 0. The narrowing is a measurement, not a skip: the population is read from eslint's own config (**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}; the changeset .md answers File ignored because no matching configuration was supplied, so 2 of 3 changed paths are lintable), the count of 2 comes from --format json, and this repo's single eslint.config.mjs never enables type-aware linting for any file — its own measured note says so — so nothing in this diff can move an untouched file's verdict.

验收备注

Noted while working, not filed — none of them is a reproducible defect, a stated-contract violation, or an authoring trap, so none of them earns a card:

  • The --json payload carries two count semantics side by side — counts is what was emitted, metadataFormsCounts is what was built. The file's own comment already says so and already says the question of whether it should is the maintainer's. Untouched here.
  • A drifted --check --json run now reports that the bundles drifted but not which ones, where the console face names each file. That is the widening described under Clause-② above, deliberately left to the PM to route.
  • --check is the only flag of its kind in this CLI (packages/cli/src/commands/), so there was no same-mode precedent to copy — only same-command and same-family ones. Recorded so the next reader does not re-derive it.

Opened from the os-dev seat using Claude Code; this sentence is the durable attribution, since a trailing footer block on a PR body is rewritten by the platform.


Generated by Claude Code

…#16600)

The machine face returned before anything was compared: `if (flags.json) {
… return; }` sat ahead of both the `--check` needs-`--out` guard and the
comparison block, so `--check --json` exited 0 on a tree whose bundles had
provably drifted. Driven on one fixture, two invocations differing only by
`--json`: the first exited 1 reporting `missing:` and the drift sentence, the
second exited 0 with the ordinary payload. Same shape as the `--dry-run`
branch in #16480.

Under `--json`, `--check` is a verdict mode, so the comparison now runs before
the one document the run is allowed to write. Drift leaves through this
command's existing `{ error, …errorCodeFields }` envelope with exit 1 — no
new payload member — and the needs-`--out` refusal is reachable there too. An
in-sync tree and a `--json` run that did not ask for `--check` are unchanged.

The file list and the comparison are now one closure each (`emittedFiles`,
`compareCommitted`, `driftMessage`), read by both faces, so the console and
machine `--check` cannot diverge about what this run produces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 6 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/i18n-standard.mdx (via os i18n extract (command, read off packages/cli/src/commands/i18n/extract.ts))
  • content/docs/ui/translations.mdx (via os i18n extract (command, read off packages/cli/src/commands/i18n/extract.ts))

3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v15.mdx (via os i18n extract (command, read off packages/cli/src/commands/i18n/extract.ts))
  • content/docs/releases/v16.mdx (via os i18n extract (command, read off packages/cli/src/commands/i18n/extract.ts))
  • content/docs/releases/v17.mdx (via os i18n extract (command, read off packages/cli/src/commands/i18n/extract.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 67ccfe8efb815e7b43c16b0b6c095e2a240e28e2packageMentionDocs.

Which tree this was computed on

This run read content/docs from 434297d59130c3d9013dec72e6663eb1e690388b — the merge of head 3fd7a6b0a6ad2f28342e222df3b2cfb20af66a10 into base 67ccfe8efb815e7b43c16b0b6c095e2a240e28e2, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 434297d59130c3d9013dec72e6663eb1e690388b && git checkout 434297d59130c3d9013dec72e6663eb1e690388b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 67ccfe8efb815e7b43c16b0b6c095e2a240e28e2 3fd7a6b0a6ad2f28342e222df3b2cfb20af66a10 && git checkout -B drift-repro 67ccfe8efb815e7b43c16b0b6c095e2a240e28e2 && git merge --no-ff 3fd7a6b0a6ad2f28342e222df3b2cfb20af66a10

node scripts/docs-audit/affected-docs.mjs --json 67ccfe8efb815e7b43c16b0b6c095e2a240e28e2

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 67ccfe8efb815e7b43c16b0b6c095e2a240e28e2 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

… block

The note said the `{ error, …errorCodeFields }` envelope sits "twenty lines
down"; it is at the end of the method, ~200 lines below. Comment only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
…uling

The PM ruled clause-② `yes` on card #16600: copying this command's existing
`{ error, ... }` envelope onto a path that could not reach it is the widening
branch triage enumerated. `check-changeset-no-major`'s way 1 then applies —
the declaration is right and the level was wrong. Records why no BREAKING
banner rides with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ

@os-sales os-sales left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At-tier contract review (CONTRACT_REVIEW_TIER) — card #16600

Dispatched by the domain:cli PM seat (#6024) with the Clause-② classification itself explicitly delegated. Reviewed at head 75fc1c3423 (code identical to 7956a7c308; cba32a42b9 is comment-only, 75fc1c34 is changeset-only). Verification ran in a dedicated worktree at this head with the CLI's dependency closure built.

Verdict: PASS-WITH-FINDINGS · Clause-②: no

One finding must be fixed before this goes ready (F1 — the machine face's regenerate command). The contract question is settled below. ⛔ This is a verdict, not an approval; the queue and the maintainer decide.


The ruling: Clause-②: no — the published face did not move; a declared contract was enforced

The criterion, from the rulebook rather than from memory: Clause-② asks whether the card relaxes the accept set or expands the public face (.claude/skills/pm-dispatch/SKILL.md — 「判据:本卡放宽接受集或扩大公开面吗」; 「条款②只指已发布契约面」), and the same rulebook says 「拉回已声明契约按常规档」 — pulling behaviour back to an already-declared contract is the regular tier. The lane spec is direction-explicit: 「放宽接受集或扩大公开面的卡,不论多小,即条款②;收窄仍是语义面,不触条款②」 (references/lanes/spec.md). Measured against the diff:

  1. Accept set — narrowed toward the declared contract, not relaxed. Nothing is newly accepted. Two inputs that were silently accepted against the declared contract are now refused as the contract says. The contract text: the --check flag's own help (extract.ts): "Write nothing; fail if the committed bundles in --out differ from a fresh extract"; and content/docs/ui/translations.mdx: "Fail if the committed bundles have fallen behind the schema" / "--check writes nothing: it re-renders and diffs against --out". The exit 0 on a drifted tree under --json was never a documented behaviour of the flag pair — no page documents the pair at all (i18n-standard.mdx documents --json alone as "Print the skeletons as JSON instead of writing files"). The 0 → 1 transition is the repair of a check that never checked, which is exactly the 「拉回已声明契约」 case.
  2. Public face — unchanged in shape. Under --json, stdout could already carry exactly two documents: the extract payload, or { error, ...errorCodeFields } from the catch (extract.ts:745-749, verified — identical to i18n/check.ts:186 and lint.ts:995). After this PR it can carry the same two, and only one per run. No member added, removed or retyped; no new error code minted (the drift Error carries none, so errorCodeFields yields {} — a new code would have been an automatic yes per the ERROR_CODE_LEDGER rule); the error sentence's value is, by format.ts's own account, "an English sentence that no contract pins". The exit-code set stays {0, 1}. node scripts/pm/check-widening-tells.mjs --declaration no --diff - over this diff: 3 files read, no widening tell, exit 0.
  3. Triage's own scoping agrees. Triage split the card and wrote of the exit-code half: 「⭐ 这一条就足以杀死假绿,且不新增任何公开输出形状」 — needing no ruling. The ruling-needing half was the payload shape. The sentence the PM relied on — 「若照抄了既有约定 ⇒ 那是加宽已发布输出」 — infers a widening from the copying; that inference is the factual predicate delegated to this tier, and on this diff it does not hold: copying the catch envelope produced no payload shape. The dev delivered the no-ruling half and left the widening (naming the drifted files) unrouted, which triage forbade settling here.
  4. Precedent, same command, same class, today. #16480 (the --dry-run twin, PR #16627): a documented flag pair went from exit 0 to exit 1 on drift; claimed Clause-②: no ("CLI flag semantics inside the dev's remit — no contract review needed"), graded patch, shipped with the same "pipelines may go red" disclosure, merged. The PM's argument (b) — a documented pair now exits 1 — applied identically there. The only candidate discriminator here is the envelope routing, and (2) shows it adds no shape.
  5. The level rule corroborates. "WHICH LEVEL" (pr-automation.yml, maintainer 2026-09-04 batch #35): "a fix( that changes no public surface stays patch." A yes here forces minor for a change that grew no surface — the self-contradiction the level axis exists to catch, reached from the other side.

On the PM's asymmetry argument: it is right, and it is why this review happened; the conservative default did its job by landing the question here. The one repo precedent of an accept→reject flip graded yes (plugin-approvals C-a/C-b) is a runtime approver-screening change — the rulebook's 「运行时权限/安全行为变更不是条款②」 negative boundary — and does not carry over to a CLI exit code enforcing its own help text.

What the ruling changes on this PR (for the PM to route; ⛔ none of it is mine to write):

  • Card: the governing Claim: comment (5573754600) is the carrier cardDeclaration reads — a re-judgement must be edited into that comment (or a newer Claim: naming the same branch); a separate ruling comment is not read once the claim itself declares yes. Then clear needs:contract-review from both carriers.
  • PR body: replace the heading ## Clause-②: no with a bare line Clause-②: noreadClause2Line reads the heading as a near-miss (that is what Check Changeset reported), and with the carrier off the level axis then reads not-declared rather than not-measured.
  • Changeset: back to patch, and drop the paragraph added in 75fc1c34 that ties the grade to the PM's yes — under this ruling that paragraph states a reason that no longer exists.

Findings

F1 — must fix before ready · the --json drift envelope names a regenerate command that regenerates nothing. rerunWithoutCheck removes only --check from argv, so under --json the prescribed remedy keeps --json. Measured at this head on the card's fixture:

$ os i18n extract CONFIG --locales=zh-CN --no-metadata-forms --out=OUT --check --json
{"error":"Translation bundles have drifted from the schema. Regenerate and commit:\n  os i18n extract CONFIG --locales=zh-CN --no-metadata-forms --out=OUT --json"}
-> exit 1
$ os i18n extract CONFIG --locales=zh-CN --no-metadata-forms --out=OUT --json   # the named command
-> exit 0, files in OUT: 0

--json is "Output JSON instead of writing files", so an operator (or a CI log reader) who does what the failure says gets a payload, writes nothing, and the next --check --json fails again with the same advice — the #14895 loop ("the failure is self-healable and the advice is what stops it healing") on the face this PR creates. The changeset's consumer-facing line "The fix is the one the failure names" is therefore wrong under --json. Fix within remit: drop --json (and --json=…) alongside --check when building the rerun line — the function's contract is "the command that regenerates these bytes", and a --json run is not that — and reword its "removes one token" note; add one assertion to the two drift cases that the error's second line contains --out= and not --json (today jsonVerdict reads only the first line, which is why the pin did not catch this).

F2 — PR body declaration spelling. See above: a bare Clause-②: no line. The fixed spelling readClause2Line accepts is Clause-②: yes|no at line start (optionally blockquoted / bulleted / bolded), not a heading.

F3 — changeset grade and banner. Under no: patch, per WHICH LEVEL. The dev's reasoning for no **BREAKING** banner and no ADR-0087 disposition is right and I concur on the merits, independent of the level: nothing an author can write is removed or narrowed, the failure prescribes no consumer code change, and ADR-0087 is the metadata migration ledger (check-adr-0087-registration.mjs) — a CLI exit code enforcing its documented semantics is not a migration. The relayed rule ("breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition, not by the level") is verified verbatim at pr-automation.yml WHICH LEVEL and in check-changeset-no-major.mjs's header. The ⚠️ disclosure paragraph is the right instrument and reaches consumers through @objectstack/cli's CHANGELOG; amend its remedy sentence once F1 lands ("re-run without --check — and without --json").

Items judged, with evidence

  • Envelope choice — correct. { error, ...errorCodeFields }, compact, exit 1 is this command's only machine-readable failure face (catch, extract.ts:745-749) and the family's (i18n check, lint). Routing the needs---out refusal there is not a second silent change: it is the other thing the same early return skipped, listed as row 3 of the card's own table, reached by the same thrown Error.
  • Single-document purity — holds. By reading: the only stdout writes ahead of the --json branch are guarded (if (!flags.json) printHeader/printStep/printInfo); under --json the payload is emitted only after the verdict (extract.ts:557-563), and the catch is the only other stdout writer. isExitSignal (format.ts:129) is real and the two-document reasoning is right — payload-then-envelope would parse as neither one document nor JSONL. The pin asserts documents: 1 on every machine run.
  • The deliberate non-widening — right, and the asymmetry is defensible as shipped once F1 is fixed. The machine face reports that, not which; the actionable branch for a pipeline is exit 1 plus a working regenerate command. Naming the files is additive, follows os lint --json / os i18n check --json's report-plus-derived-exit shape, and is a Clause-②: yes, minor card of its own. Without F1 the machine face would have neither the file list nor a usable remedy, which is why F1 is a blocker and not a nit.
  • The pin — discriminates. Ablation at this head: base blob bf9f8909e0 checked out under the finished branch (hash-proven), vitest --project integration3 failed / 2 passed: the two drift cases and the needs---out case red in the predicted direction ({ status: 0, error: null, payload: true } against { status: 1, error: "Translation bundles have drifted…", payload: false }), in-sync and no---check green on both sides as designed; restore proven by blob abcb73a72b and an empty git status --porcelain. On HEAD: 5/5. Tier: vitest-tiers.ts classifies the file integration on childProcess, entryBasename, tsxBin, and without the .e2e name it is in the OS_TEST_TIERS=queue population (vitest.config.ts), so it runs per-PR — the right trade for a regression that reads green. error is asserted as a first-line string, payload as a boolean, and the two-invocation equality is stated beside spelled-out values, so two runs that both compare nothing cannot satisfy it.

CI at review time: Check Changeset red is the level axis reading the carrier as yes against patch (now minor at 75fc1c34) — resolved by the ruling above, not evidence for either side; everything else on the head green or in flight.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ


Generated by Claude Code

…tes nothing

`rerunThatRegenerates` (was `rerunWithoutCheck`) stripped only `--check`, so on
the machine face the command it named kept `--json` — "output JSON instead of
writing files". Running exactly what the failure printed emitted a payload,
wrote zero files, and left the next `--check --json` failing with the same
advice: the #14895 loop reproduced on the face this branch creates. Measured
end to end on the card's fixture; after the change the printed command writes
the bundle and the following `--check --json` exits 0.

The degraded fallback line names the same two tokens under `--json`, so the
built command and the fallback cannot prescribe different things.

The pin reads the remedy as well as the sentence: the drift envelope is two
lines and the second one is where this face can go wrong on its own, which is
why reading only line one let this through. `remedyNamesOut` /
`remedyCarriesJson` are asserted on every machine case.

Changeset back to `patch` under the at-tier `Clause-②: no` ruling, and its
remedy sentence amended to name both flags.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: os i18n extract --check --json exits 0 having compared nothing — the second false green on this command

2 participants