Skip to content

[finding] os validate --json --strict exits 0 on a config the text-mode --strict exits 1 for — the JSON path returns above the strict check #11174

Description

@os-elon

Found while implementing #10953 (which fixes a different divergence in the same file — four advisories being unreachable in the --json payload). Filed unassigned, not routed, not graded. Not fixed there: #10953 is scoped to advisory reachability, while this is about exit-code semantics, and an exit code is a declared contract (CliExitCode in packages/cli/src/utils/format.ts exists precisely to keep that slot honest). Changing it is a decision, not a mechanical repair.

Measured

At main head plus the #10953 branch, on a config that raises four non-blocking warnings (no manifest, no objects, no apps):

$ os validate --strict ; echo "exit=$?"
...
⚠ No objects defined — this stack has no data model
⚠ No apps or plugins defined — this stack may not do much
⚠ Missing manifest.id — required for deployment
⚠ Missing manifest.namespace — required for multi-app hosting
✗ Strict mode: warnings treated as errors
exit=1
$ os validate --json --strict ; echo "exit=$?"
{ "valid": true, ..., "warnings": [ ...four entries... ] }
exit=0

Same config, same flag, opposite exit status.

Why

In packages/cli/src/commands/validate.ts the --json branch emits the payload and returns. The only flags.strict reader sits below that return, inside the text-rendering block:

if(flags.json){awaitemitJson({ ... });return;}// never sees flags.strict
...
if(warnings.length>0){
...
if(flags.strict){printError('Strict mode: warnings treated as errors');this.exit(1);}}

So --strict is accepted, documented, and inert whenever --json is also passed. content/docs/deployment/cli.mdx:1712 documents os validate --json --strict as a combination, which is the audience this silently no-ops for: a CI step spelling both flags reads exit 0 and concludes the stack is clean.

Note #10953 reduces the blast radius without closing this: after it, a pipeline can at least gate on warnings.length itself. A pipeline trusting the exit status still cannot.

Open decision, not an obvious fix

Whether --json --strict should exit 1 is a real design call, which is why this is filed rather than patched:

  • Exit 1 to match text — consistent, and what the documented flag combination implies. But it flips the exit status of existing green CI runs.
  • Keep exit 0 and let the consumer gate on the payload — arguably the --json contract already: the payload is the product, the exit status reports whether the command ran. Then --strict should arguably be refused alongside --json rather than silently ignored.

Either way, "accepted and silently inert" is the one option that is clearly wrong.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions