Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions scripts/check-i18n-bundles.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -991,9 +991,11 @@ function reportPrerequisiteNotMet(headline, detail, options = {}) {
alsoFix.map((l) => ` ${l}\n`).join('') +
`\n${nothingChecked}\n` +
` (Exit code 1 — capture it BEFORE any pipe: \`pnpm check:i18n > /tmp/i18n.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the pipe's status, and \`| head -N\` turns even \`\${PIPESTATUS[0]}\`/\`pipefail\` green:\n` +
` \`head\` closes the read end early, so this gate takes EPIPE and exits 0 — the pipe changed the\n` +
` exit code itself. \`| tail\` reads to EOF; \`\${PIPESTATUS[0]}\` after it is the true status.)`,
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
}
Expand Down
27 changes: 21 additions & 6 deletions scripts/check-i18n-coverage.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1133,8 +1133,13 @@ function reportPrerequisiteNotMet(headline, detail) {
` Nothing was measured: no config was linted and no count was compared, so this\n` +
` result says NOTHING about whether any declared label went untranslated — and\n` +
` the baseline was left exactly as committed (\`--update\` included).\n` +
` (Exit code 1 — but piping this gate reports the PIPE's status, so\n` +
` \`pnpm check:i18n-coverage | tail -4\` reads green either way. Use \`echo "EXIT=$?"\`.)`,
` (Exit code 1 — capture it BEFORE any pipe:\n` +
` \`pnpm check:i18n-coverage > /tmp/i18n-coverage.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
}
Expand Down Expand Up @@ -1188,8 +1193,13 @@ function reportUnmeasuredConfigs(failures, measuredCount) {
` \`--update\` would freeze the survivors while silently dropping the rest. So this\n` +
` result says NOTHING about whether any declared label went untranslated, and the\n` +
` baseline was left exactly as committed (\`--update\` included).\n` +
` (Exit code 1 — but piping this gate reports the PIPE's status, so\n` +
` \`pnpm check:i18n-coverage | tail -4\` reads green either way. Use \`echo "EXIT=$?"\`.)`,
` (Exit code 1 — capture it BEFORE any pipe:\n` +
` \`pnpm check:i18n-coverage > /tmp/i18n-coverage.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
}
Expand Down Expand Up @@ -1217,8 +1227,13 @@ function reportEmptyPopulation(verdict) {
` Nothing was measured: no config was linted and no count was compared, so this\n` +
` result says NOTHING about whether any declared label went untranslated — and\n` +
` the baseline was left exactly as committed (\`--update\` included).\n` +
` (Exit code 1 — but piping this gate reports the PIPE's status, so\n` +
` \`pnpm check:i18n-coverage | tail -4\` reads green either way. Use \`echo "EXIT=$?"\`.)`,
` (Exit code 1 — capture it BEFORE any pipe:\n` +
` \`pnpm check:i18n-coverage > /tmp/i18n-coverage.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(1);
}
Expand Down
10 changes: 7 additions & 3 deletions scripts/check-test-completeness.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,13 @@ function prerequisiteNotMet({ headline, detail, fix }) {
' this result says NOTHING about whether every test vitest counted actually ran,',
' nor about whether every scheduled package reported.',
' ⛔ It is NOT a finding, and it is not evidence that anything in the tree is wrong.',
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from a finding's ${EXIT_FINDINGS} — but a pipe reports the PIPE's`,
' status, so `node scripts/check-test-completeness.mjs | tail -4` reads green either',
' way. Capture `echo "EXIT=$?"` BEFORE any pipe.)',
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from a finding's ${EXIT_FINDINGS} — capture it BEFORE any pipe:`,
' `node scripts/check-test-completeness.mjs > /tmp/test-completeness.log 2>&1; echo "EXIT=$?"`.',
" Piped, `$?` is the LAST command's status, and `head`/`tail` essentially never fail — that",
' is the false green, and no pipe shape repairs it. `${PIPESTATUS[0]}`/`pipefail` do recover',
" this gate's own code: `| tail` reads to EOF and forwards it, while `| head -N` closes the",
' read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that',
' dies on SIGPIPE reports 141 rather than what it meant to say.)',
],
};
}
Expand Down
74 changes: 70 additions & 4 deletions scripts/import-prerequisite.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -394,21 +394,61 @@ export async function requireDefaultExport(specifier, load, importerUrl, options
* The shared frame, in `check-i18n-coverage.mjs`'s wording and order: what is
* unmet, why, the command that clears it, and — load-bearing — that nothing was
* measured, so the exit code says nothing about the gate's actual question.
*
* ── The pipe-shape advisory, and why it says what it says ──────────────────
*
* Every importer of this module inherits the closing paragraph verbatim, so a
* wrong claim there is wrong in every gate at once. Measured here 2026-08-31 on
* node 22.22.2 / bash 5, one refusing gate plus constructed producers:
*
* node scripts/check-test-completeness.mjs -> 3 (no pipe)
* … 2>&1 | tail -4 $? = 0 ${PIPESTATUS[0]} = 3 pipefail -> 3
* … 2>&1 | head -1 $? = 0 ${PIPESTATUS[0]} = 3 pipefail -> 3
*
* So the false green is `$?` after ANY pipe — it is the LAST command's status,
* and `head`/`tail` both essentially never fail. It is NOT a property of one
* shape, and choosing a different shape does not repair it.
*
* ⚠️ `| head -N` does close the read end early and the producer DOES take EPIPE
* — proven by a producer that prints what it caught — but node ignores SIGPIPE
* and swallows the stdout write error, so the gate still reaches its own exit:
* a producer instrumented to exit 7 reported `${PIPESTATUS[0]}` = 7 through
* `| head -1`. ⛔ Do NOT write that `| head` turns `${PIPESTATUS[0]}`/`pipefail`
* green; it does not, and an earlier draft of this advisory said so. What `head`
* really costs is the VERDICT TEXT (truncated), and — for a producer that does
* not ignore SIGPIPE, unlike node — a real code replaced by 141: `seq 1
* 100000000 | head -1` reports `${PIPESTATUS[0]}` = 141. That is a false RED,
* the opposite direction.
*
* `selfTest` pins the four load-bearing clauses below.
*/
export function reportPrerequisiteNotMet(importerUrl, verdict, measures) {
console.error(prerequisiteNotMetText(importerUrl, verdict, measures));
process.exit(1);
}

/**
* The text `reportPrerequisiteNotMet` prints, as a value — so the self-test can
* assert on the advisory without spawning a process or stubbing `process.exit`.
*/
function prerequisiteNotMetText(importerUrl, verdict, measures) {
const gate = fileURLToPath(importerUrl).split('/').pop().replace(/\.mjs$/, '');
const subject = measures ? `whether ${measures}` : `what it gates`;
console.error(
return (
`\n${gate}: PREREQUISITE NOT MET — ${verdict.headline}\n\n` +
verdict.detail.map((l) => (l ? ` ${l}` : '')).join('\n') +
`\n\n Fix: ${verdict.fix}\n\n` +
` Nothing was measured: this gate exited before running a single check, so this\n` +
` result says NOTHING about ${subject}. It is NOT a finding, and it is not\n` +
` evidence that anything in the tree is wrong.\n` +
` (Exit code 1 — but piping this gate reports the PIPE's status, so\n` +
` \`node scripts/${gate}.mjs | tail -4\` reads green either way. Use \`echo "EXIT=$?"\`.)`,
` (Exit code 1 — capture it BEFORE any pipe:\n` +
` \`node scripts/${gate}.mjs > /tmp/${gate}.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`
);
process.exit(1);
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -586,6 +626,32 @@ export function selfTest() {
rmSync(dir, { recursive: true, force: true });
}

// ── the inherited pipe-shape advisory ───────────────────────────────────
// Pinned HERE and nowhere else, because this is the one copy 45 importers
// print. The clauses are the four the advisory is for; the negative one is
// the load-bearing one, since the wrong claim it excludes reads perfectly
// plausible and shipped once already.
const advisory = prerequisiteNotMetText(
new URL('file:///repo/scripts/check-fixture-gate.mjs').href,
{ headline: 'h', detail: ['d'], fix: 'f' },
undefined,
);
t('the advisory prescribes capturing the code BEFORE any pipe',
advisory.includes('capture it BEFORE any pipe') && advisory.includes('> /tmp/check-fixture-gate.log 2>&1'),
advisory);
t('the advisory names the shape-independent false green: `$?` is the LAST command\'s status',
advisory.includes("`$?` is the LAST command's status") && advisory.includes('no pipe shape repairs it'));
t('the advisory keeps `| tail` as the shape that FORWARDS the true status',
advisory.includes('`| tail` reads to EOF and forwards it'));
t('the advisory names `| head -N` and the EPIPE mechanism, with its real cost',
advisory.includes('`| head -N` closes the') && advisory.includes('EPIPE')
&& advisory.includes('TRUNCATED') && advisory.includes('141'));
// ⛔ The claim this gate must never make again: measured 2026-08-31, `| head`
// does NOT defeat `${PIPESTATUS[0]}`/`pipefail` — node ignores SIGPIPE and
// reaches its own exit. See the mechanism note on `reportPrerequisiteNotMet`.
t('the advisory does NOT claim a pipe shape defeats `${PIPESTATUS[0]}`/`pipefail`',
!/turns even .*PIPESTATUS.*green|reads green either way/.test(advisory));

const failed = cases.filter((c) => !c.ok);
for (const c of failed) console.error(` ✗ ${c.name}${c.detail ? ` -- ${c.detail}` : ''}`);
if (failed.length) {
Expand Down
9 changes: 7 additions & 2 deletions scripts/pm/check-half-states.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8502,8 +8502,13 @@ function reportPrerequisiteNotMet(v, options = {}) {
` per label", because a first page is not a count and this gate could not be run to get one.\n` +
` ⇒ Treat this exit as an unread instrument, never as a quiet board: the H39 census below\n` +
` does not appear at all in a run that ends here.\n` +
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from the unclassified failure's 2 — but piping this\n` +
` reports the PIPE's status, so \`… | tail -4\` reads green either way. Use \`echo "EXIT=$?"\`.)`,
` (Exit code ${EXIT_PREREQUISITE_NOT_MET}, distinct from the unclassified failure's 2 — capture it BEFORE\n` +
` any pipe: \`node scripts/pm/check-half-states.mjs > /tmp/half-states.log 2>&1; echo "EXIT=$?"\`.\n` +
` Piped, \`$?\` is the LAST command's status, and \`head\`/\`tail\` essentially never fail — that\n` +
` is the false green, and no pipe shape repairs it. \`\${PIPESTATUS[0]}\`/\`pipefail\` do recover\n` +
` this gate's own code: \`| tail\` reads to EOF and forwards it, while \`| head -N\` closes the\n` +
` read end early — the gate takes EPIPE, its verdict text is TRUNCATED, and a producer that\n` +
` dies on SIGPIPE reports 141 rather than what it meant to say.)`,
);
process.exit(EXIT_PREREQUISITE_NOT_MET);
}
Expand Down
18 changes: 15 additions & 3 deletions scripts/pm/ci-failure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@
* environment exactly as much as one that was dead at
* request 1.
*
* Piping hides all of it (`… | tail` reports the PIPE's status). Read `$?`.
* Piping hides all of it: after ANY pipe `$?` is the LAST command's status, and `head`/`tail`
* essentially never fail. Capture the exit code BEFORE any pipe; `${PIPESTATUS[0]}`/`pipefail`
* recover it, `| tail` reading to EOF while `| head -N` truncates the verdict text.
*
* ## The transport — node's fetch does not read HTTPS_PROXY, and the failure
* wears a credential fault's face
Expand Down Expand Up @@ -1537,7 +1539,12 @@ function render(result, target) {

say(`(Exit ${verdict.exit}: 0 green · 1 red with the assertion in hand · 2 undetermined · ` +
`${EXIT_PREREQUISITE_NOT_MET} prerequisite not met.`);
say(" Piping reports the PIPE's status, so `... | tail` reads green either way. Use `echo 'EXIT=$?'`.)");
say(" Capture it BEFORE any pipe: `node scripts/pm/ci-failure.mjs > /tmp/ci-failure.log 2>&1; echo \"EXIT=$?\"`.");
say(" Piped, `$?` is the LAST command's status, and `head`/`tail` essentially never fail — that is the");
say(" false green, and no pipe shape repairs it. `${PIPESTATUS[0]}`/`pipefail` do recover this tool's own");
say(" code: `| tail` reads to EOF and forwards it, while `| head -N` closes the read end early — the tool");
say(" takes EPIPE, its verdict text is TRUNCATED, and a producer that dies on SIGPIPE reports 141 rather");
say(" than what it meant to say.)");
return { text: out.join('\n'), exit: verdict.exit, verdict };
}

Expand Down Expand Up @@ -2421,7 +2428,12 @@ async function reportMidWalkFailure(error, stage) {
console.error(`\nci-failure: ${decision.verdict} — ${decision.headline}\n`);
for (const line of decision.detail) console.error(line ? ` ${line}` : '');
for (const line of renderFixLines(decision.fix)) console.error(line);
console.error(" Piping reports the PIPE's status, so `... | tail` reads green either way. Use `echo \"EXIT=$?\"`.");
console.error(" Capture the exit code BEFORE any pipe: `node scripts/pm/ci-failure.mjs > /tmp/ci-failure.log 2>&1; echo \"EXIT=$?\"`.");
console.error(" Piped, `$?` is the LAST command's status, and `head`/`tail` essentially never fail — that is the");
console.error(" false green, and no pipe shape repairs it. `${PIPESTATUS[0]}`/`pipefail` do recover this tool's own");
console.error(" code: `| tail` reads to EOF and forwards it, while `| head -N` closes the read end early — the tool");
console.error(" takes EPIPE, its verdict text is TRUNCATED, and a producer that dies on SIGPIPE reports 141 rather");
console.error(" than what it meant to say.");
process.exit(decision.exit);
}

Expand Down
Loading