Skip to content

[finding] check:i18n-coverage resolves its population CWD-relatively, so an off-root run reports OK over a scan of nothing #10907

Description

@claude

Observation-class, measured while reading gate populations for #10840. No gate is red — CI invokes this gate from the repo root, where it behaves correctly. Filed unassigned, no pm:queue.

The shape

scripts/check-i18n-coverage.mjs resolves its example population CWD-relatively, unlike every other scan gate in scripts/, which joins against a REPO_ROOT/ROOT derived from import.meta.url:

constEXAMPLES_DIR='examples';// not join(ROOT, 'examples')functiondiscoverExamples(){if(!existsSync(EXAMPLES_DIR))return[];// <- an empty POPULATION, not an error
...
}

BASELINE_PATH is spelled the same way.

Why it matters: the empty scan reports OK

Run from anywhere other than the repo root, the guard returns [] and the gate goes green over a scan of nothing:

$ cd /tmp && node .../scripts/check-i18n-coverage.mjs
check-i18n-coverage: could not read packages/cli/package.json (ENOENT ...) - build prerequisite not pre-checked
check-i18n-coverage: OK (0 config(s), 0 baselined untranslated string(s), none new).
exit 0

OK (0 config(s)) is the #4690 shape this repo refuses elsewhere by name — "zero is a broken scan, not a clean repo". trackedFiles in scripts/pm/dispatch-gates.mjs throws on an empty listing for exactly this reason, and its docblock argues the case. Here the same condition is a silent pass, and the ENOENT line above it is printed as a note rather than a verdict.

Not urgent, and why it is worth recording anyway

CI runs it from the repo root, so nothing is red today and this is not a request to treat it as such. What makes it worth a card is that the failure is invisible in the direction that matters: a green line over a population of zero is indistinguishable from a green line over the real population, so if the invocation ever moves — a new workflow step with a working-directory:, a wrapper script, a dev running it by hand from a package dir — the gate stops checking anything and says so in the same words it uses when it passes.

Direction (not a decision)

Two independent halves, either useful alone:

  1. Join both constants against a module-derived root, as the sibling gates do — check-skills-token-ratchet.mjs and check-ratchet-remedy-authority.mjs are the nearest models.
  2. Refuse the empty population rather than returning it, the way trackedFiles does — so an unreadable input can never look like an empty answer.

Refs

#10840 (where this was found — that card's own subject is a different property of the same constant) · trackedFiles' docblock in scripts/pm/dispatch-gates.mjs for the #4690 argument this is the counter-example to


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions