Skip to content

[finding] os lint --eval --json has no JSON face for an uncaught throw — runEval is dispatched above the try, so the error escapes as oclif's human output #14974

Description

@os-trump

Filed unassigned, recording only — found while censusing the JSON exits of packages/cli/src/commands/lint.ts for #14015 (PR #14973). Not in that card's scope, not touched by it.

The reading

packages/cli/src/commands/lint.ts:

  • run() dispatches eval mode at L516if (flags.eval) { await this.runEval(flags, timer); return; }
  • the project-lint try opens at L561 and its catch-all JSON exit is at L722-730

So the eval branch runs entirely above the try. Nothing thrown out of runEval can reach that catch-all. runEval has one internal try of its own (L778), covering bundleRequire of --generator only; a throw from runMetadataEval or from anything else below it is uncaught inside the command.

The command also hand-rolls its json flag as a plain Flags.boolean rather than using oclif's enableJsonFlag, so oclif wraps nothing: there is no framework-level JSON error envelope to fall back on.

⇒ Net effect: a caller that ran os lint --eval --json and hits an uncaught throw gets oclif's human error text, not a JSON document. A machine consumer parsing stdout gets a parse failure rather than an error payload — and the two project-lint exits it may have been written against both do produce JSON on a throw (L730), so the shape is inconsistent across the same command's modes.

Why it is worth recording

This is the same family the conversions and warnings cards kept landing in — "the machine face is missing on some exit of a command that advertises --json" — one mode over. #12125 and #13741 both settled that every failure exit of an authoring command should carry the same shape; the --eval mode was never brought under that rule, and this is a stronger version of the gap than a missing key: there is no payload at all.

⚠️ What is NOT claimed

I did not construct a reproducer. This is a structural reading of the control flow at the line numbers above, not a measured crash. Whoever picks it up should first establish that a throw is actually reachable there — runMetadataEval captures per-case generationError internally (packages/cli/src/lint/metadata-eval.ts), so the reachable throw sites may be narrow, and "narrow enough not to be worth a contract move" is a legitimate outcome. The bug, if any, is the missing face, not a specific crash.

Related, and deliberately separate

⛔ This is not the "make the --eval exits emit conversions" branch that #14015 fenced off as needing its own card and an at-tier contract review. That one is about a key on two exits that already produce JSON. This one is about a path that produces none. They touch the same mode and the same file, so they may well want reviewing together, but they are different defects and neither implies the other.

Refs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions