Skip to content

[finding] cli checklist: build-own-contract clause 4 vs flag-command-error-ux disagree on exit code 2 — narrow clause 4 to the #4873 duration-leak #7648

Description

@huangyiirene

Symptom

Two items in the cli checklist area disagree about exit code 2. cli.build-own-contract clause 4 (docs/qa/platform-checklist/areas/cli.json:145 — "exit codes are exactly 0 or 1 (the CliExitCode union) — never a count, never a duration") treats a config-load-time throw that exits 2 as a violation, while cli.flag-command-error-ux asserts that exit 2 IS the correct oclif ExitError code. In the run, a config-load-time throw on the human path exits 2 (reproduced twice) because packages/cli/src/commands/compile.ts's catch ends in this.error() and oclif's ExitError is 2; the CliExitCode 0|1 type only narrows the emitJson/emitText slot. Every --json invocation exits cleanly (0/1/1/1).

Root cause

Checklist reconciliation, not a product defect. Clause 4 was written to pin the #4873 duration-in-exit-slot leak (a timer value reaching the exit-code slot), not to forbid oclif's standard error exit. As phrased it collides with flag-command-error-ux, which correctly treats exit 2 as the oclif error exit. The clause should be narrowed to the #4873 duration-leak it exists to catch, so a legitimate oclif ExitError (2) on a config-load-time throw no longer reads as a violation.

Reproduction

Run a build/compile invocation whose config fails to load at load time on the human (non---json) path; observe exit 2 from compile.ts's this.error() catch. Compare against cli.flag-command-error-ux, which asserts exit 2 as the correct oclif ExitError. The two checklist clauses cannot both hold as written. On origin/main the clause text (cli.json:145) and compile.ts's this.error() catch (lines 411, 480) are unchanged, so the reconciliation is still needed.

Source

Extracted from the QA run #7628 (framework 92f26f7, console 6314e87f).

Activity

  1. os-zhuang commented on Aug 11, 2026

    @os-zhuang
    Contributor

    Triage — dual-state resolution: pm:queue + domain:devx kept; finding removed (queue+finding is an illegal pair).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 11, 2026
  3. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    CollaboratorAuthor

    Claim: PM loop round 3 (devx seat, 2026-08-11 term) — wave 3 unit E, packed with #7650 (same cli.json; full claim block on #7650). Branch claude/issue-7650-7648-cli-checklist; session session_01GZKbx4xyF7U5WXj6ch49BM; mode:cloud, model: opus. This card's half: narrow build-own-contract clause 4 to the #4873 duration-leak it exists to pin, so a legitimate oclif ExitError (2) stops reading as a violation — the narrowed clause must stay load-bearing (a duration value in the exit slot must still fail it).


    Generated by Claude Code

  4. huangyiirene commented on Aug 11, 2026

    @huangyiirene
    CollaboratorAuthor

    Pointer — packed with #7650 (same file, one branch, one PR). Full os-dev report lives on #7650: #7650 (comment)

    Draft PR: #7712 (first lines Fixes #7650 / Fixes #7648).

    This card's half — cli.build-own-contract clause 4, revision 1 to 2:

    • Before: "exit codes are exactly 0 or 1 (the CliExitCode union) — never a count, never a duration". As phrased it also forbade oclif's standard ExitError 2, which compile.ts's human-path this.error() catches (:411, :480) legitimately produce on a config-load-time throw — so it contradicted cli.flag-command-error-ux, which asserts that same 2 is correct.
    • After: narrowed to the os migrate 成功时退出码是随机非零值(208/171/176/163/62…),--version / --help 却干净退出 0 #4873 duration-leak it exists to pin — the exit code is a STATUS, never a computed value — with the legitimate exits named explicitly: 0 and 1 via CliExitCode (every emitJson/emitText caller, i.e. both --json paths here), plus 2 as oclif's ExitError when the human path ends in this.error(). A config-load-time throw exiting 2 is the framework's error exit and is no longer a violation; cli.flag-command-error-ux is cross-referenced in source so the two cannot re-collide.
    • Still load-bearing: a duration/count reaching the exit slot still FAILS the clause, now by two tests the old wording did not have — a successful run exiting nonzero (the os migrate 成功时退出码是随机非零值(208/171/176/163/62…),--version / --help 却干净退出 0 #4873 shape: timer.elapsed() in emitJson's positional exit slot made a successful run exit 531 & 0xFF = 19), and any code that moves between two identical runs. A repeat-run step and a matching negative were added so the clause stays falsifiable, and the format.ts source note was corrected to say CliExitCode narrows the emitJson/emitText slot, not oclif's own error exit.

    Premise re-verified at 21888ab: format.ts:41 still reads CliExitCode = 0 | 1; both this.error() catches unchanged, each on the human branch while the --json branch above it answers through emitJson(...) + this.exit(1). Text-only — no packages/cli change.


    Generated by Claude Code


    Generated by Claude Code

  5. added a commit that references this issue on Aug 17, 2026
    7756490
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions