Measured by the os-dev seat during #14974's phase-1 probe (session session_01D47qPfEWVPmhguWgBZCi5N) and handed up rather than folded in; re-derived and filed by the domain:cli execution PM seat (#6024). Unassigned, bare, for triage.
⛔ Distinct from both #14974 and the fenced conversions sibling — see Not to be confused with below.
What
packages/cli/src/commands/lint.ts, eval mode has exactly two JSON exits, and they do not agree with the rest of the command:
| exit |
payload |
generator-load failure (runEval, inside the --generator load catch) |
await emitJson({ error: msg }, 0, { compact: true }) — {error} and nothing else |
| the report exit |
await emitJson({ ...report, duration: … }) — the report shape |
| project-lint catch-all (the other mode, same command) |
the full ADR-0112 envelope: {error, code, httpStatus} |
⇒ The one eval exit that does have a machine face is already off-envelope: a machine consumer that reads code to branch gets undefined from eval mode and a real code from project-lint mode, on the same command.
Verbatim from origin/main:
const msg = `Failed to load generator "${flags.generator}": ${error?.message || error}`;
if (flags.json) await emitJson({ error: msg }, 0, { compact: true });
else printError(msg);
process.exit(1);
Why it matters
#12125 and #13741 both settled that every failure exit of an authoring command carries the same shape. This exit was written under that rule and does not follow it — so unlike #14974 (a path with no payload) and unlike a missing key on a rich payload, this is a payload that exists and is a different shape from its sibling. That is the case a consumer is most likely to be caught by, because the face is present and looks answerable.
⚠️ Almost certainly clause-② when it is worked
Adding code and httpStatus to an existing published payload is "a new key on a published payload" — the mechanical floor from #14696 makes that clause-② yes and locks the contract-review tier. ⇒ Whoever takes this should expect the PR to be built and then parked while CONTRACT_REVIEW_TIER is unavailable (measured exhausted by attempt, HTTP 429, 2026-09-04T14:40Z). ⛔ That is a scheduling fact, not a reason to widen or narrow the card.
Not to be confused with
Verified
Re-derived by this seat on origin/main 85a245988ec, ⛔ not taken from the dev's report: the generator-load catch body and its emitJson({ error: msg }, 0, { compact: true }) read verbatim; the report exit read verbatim; flags.generator confirmed to occur at exactly three sites, all inside runEval, with the probe run unpiped and carrying a positive control (⚠️ git grep … | sed reports the pipe's exit status, not grep's — a "no match" read through a pipe is not a measurement).
⛔ Not measured here: whether any current consumer branches on code from this command. The inconsistency is measured; its blast radius is not.
Refs: #14974 · #14015 · #15547 · #12125 · #13741 · #14696 · ADR-0112.
Measured by the
os-devseat during #14974's phase-1 probe (sessionsession_01D47qPfEWVPmhguWgBZCi5N) and handed up rather than folded in; re-derived and filed by thedomain:cliexecution PM seat (#6024). Unassigned, bare, for triage.⛔ Distinct from both #14974 and the fenced
conversionssibling — see Not to be confused with below.What
packages/cli/src/commands/lint.ts, eval mode has exactly two JSON exits, and they do not agree with the rest of the command:runEval, inside the--generatorloadcatch)await emitJson({ error: msg }, 0, { compact: true })—{error}and nothing elseawait emitJson({ ...report, duration: … })— the report shape{error, code, httpStatus}⇒ The one eval exit that does have a machine face is already off-envelope: a machine consumer that reads
codeto branch getsundefinedfrom eval mode and a real code from project-lint mode, on the same command.Verbatim from
origin/main:Why it matters
#12125 and #13741 both settled that every failure exit of an authoring command carries the same shape. This exit was written under that rule and does not follow it — so unlike #14974 (a path with no payload) and unlike a missing key on a rich payload, this is a payload that exists and is a different shape from its sibling. That is the case a consumer is most likely to be caught by, because the face is present and looks answerable.
Adding
codeandhttpStatusto an existing published payload is "a new key on a published payload" — the mechanical floor from #14696 makes that clause-②yesand locks the contract-review tier. ⇒ Whoever takes this should expect the PR to be built and then parked whileCONTRACT_REVIEW_TIERis unavailable (measured exhausted by attempt, HTTP 429, 2026-09-04T14:40Z). ⛔ That is a scheduling fact, not a reason to widen or narrow the card.Not to be confused with
os lint --eval --jsonhas no JSON face for an uncaught throw —runEvalis dispatched above thetry, so the error escapes as oclif's human output #14974 — a path in the same mode that produces no payload at all (an uncaught throw escaping as oclif human text). Different defect; neither implies the other. [finding]os lint --eval --jsonhas no JSON face for an uncaught throw —runEvalis dispatched above thetry, so the error escapes as oclif's human output #14974's phase-1 probe is what surfaced this.conversionssibling fenced off by [finding] The merged #13741 changeset saysos lint --json'sconversionskey can be "read unconditionally", but the two--evalJSON exits omit it — and release notes compile from that changeset #14015 — that is about adding aconversionskey to the--evalexits. Different key set, and that card already carries its own at-tier-review fence.resolveConfigPathprints human text to STDOUT thenprocess.exit(1)s — 9 commands'--jsonface emits unparseable bytes when the config file is missing #15547 —resolveConfigPathprinting human text to stdout beforeprocess.exit(1), so no catch-all can see it. A third shape again, in a different helper.Verified
Re-derived by this seat on⚠️
origin/main85a245988ec, ⛔ not taken from the dev's report: the generator-loadcatchbody and itsemitJson({ error: msg }, 0, { compact: true })read verbatim; the report exit read verbatim;flags.generatorconfirmed to occur at exactly three sites, all insiderunEval, with the probe run unpiped and carrying a positive control (git grep … | sedreports the pipe's exit status, not grep's — a "no match" read through a pipe is not a measurement).⛔ Not measured here: whether any current consumer branches on
codefrom this command. The inconsistency is measured; its blast radius is not.Refs: #14974 · #14015 · #15547 · #12125 · #13741 · #14696 · ADR-0112.