You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Priority: P1 Type: developer experience/API contract Size: M
Problem
Exit 2 means both usable unhandled output and ordinary argparse/bad-flag failure. Batch output concatenates pretty JSON documents. Flags are global and sometimes irrelevant. Success lacks schema/rules/source metadata; --schema is not a real I/O schema.
Acceptance criteria
Define disjoint exits for success, findings/partial, invalid sheet, usage, retrieval, and internal failure; document stability.
Prefer a status field as primary machine contract so transport/process exits do not encode multiple semantics.
Use subparsers with command-specific required arguments and flags; unknown/irrelevant flags fail consistently.
Every JSON response uses a versioned envelope with status, data, findings, meta, and errors.
--pipe emits documented NDJSON one compact object per input, preserving input order and per-record status; final process status is defined.
Provide real JSON Schemas for input and output versions.
Human text and JSON are separate modes; no PII appears unexpectedly.
Compatibility/migration tests cover at least the current public contract.
Test plan
Full command/flag matrix; all exit states unique; multi-record NDJSON parses line-by-line; partial batch failure; schema validates outputs; stdout/stderr contract tests.
Draft PR #32 adds the explicit derive/report --table-evaluation surface and
draft chaoz23/table-kit#15 proves it from cold-installed wheels.
The flag uses the shared disjoint exit lane: 0 checked clean, 1 complete with
advisories/findings, and 2 refused. It is mutually exclusive with --brief,
native behavior is unchanged when omitted, and public input/runtime failures
become typed privacy-safe shared refusals. The package also exports project_table_evaluation(report) for hosts already using the canonical Python
API.
Both PRs are hosted-green, draft, self-attested, and unreleased. This is one
versioned envelope slice; it does not close the broader CLI-001 NDJSON and
contract-generation criteria.
Audit evidence for the "disjoint exits" half of this issue — two concrete, verified instances.
1. Six of eleven verbs never assign an exit code
intake, stance, qa, snapshot, seatpack, and quiz never assign to the CLI's code variable (charactercheck/cli.py:170-195) — they print(...) and fall through. Only derive, diff, and report assign. So those six always exit 0, including when their projection embeds findings.
This is documented and intentional — SKILL.md:44 ("an exit 0 there is not a cleanliness claim") and SKILL.md:120 ("0.7.0: exit contract is per-verb") — so it is not an unnoticed bug, and it should not be treated as one.
It is a contract conflict. FAMILY.md clause 1 states that 0 = pass/clean and 1 = fail/findings "in every member." A per-verb contract where qa — the verb SKILL.md itself calls the lint pass — exits 0 while emitting findings means any agent gating on the exit code reads clean.
The decision this needs: either give the projection verbs a real 0/1 verdict, or amend FAMILY.md clause 1 to admit per-verb contracts — and if the latter, state it at the top of SKILL.md rather than in a changelog line at :120, since agents read the top.
2. The usage-error lane shares exit 2 with the honest lane
charactercheck derive examples/sample-character.json --for-dm → exit 2, 0 bytes on stdout, JSON on stderr. Argparse usage errors also return 2.
SKILL.md documents exit 2 only as "unhandled/unsupported content present (the honest lane)" and instructs agents not to retry it. So an agent that makes a malformed call gets the honest lane's exit code, no stdout, and a documented instruction to escalate to a human — when the correct response is to fix the call.
srdcheck separates these: bad flags return 3, documented as "a usage/internal error (fix the call), never a verdict."
Suggested: make the exits disjoint per this issue's title, or document the usage-error lane explicitly so it is classifiable.
Evidence from a conformance audit of the check-family four against FAMILY.md v1, 2026-08-17. Commenting here rather than opening a duplicate, since this issue already owns the area.
The other two members have now landed this fix, and charactercheck is the last one — but it is blocked on a decision, not on work.
What landed elsewhere
chaoz23/dmcheck#20 and chaoz23/table-kit#30 moved usage errors off exit 2 onto exit 3, per FAMILY.md clause 1 ("usage errors are not the honest lane and should not share its exit code; srdcheck's exit 3 is the family precedent"). Both are now conformant on this rule.
Why charactercheck cannot just copy that
Exit 3 is already taken here. FAMILY.md clause 1 records charactercheck's contract as 2 = unhandled content, 3 = could-not-retrieve — and SKILL.md:126 ties exit 3 to the 0.5.1 action field and doctor. So the family precedent does not fit, and the code has to be chosen.
The options
A. Usage errors → exit 4. Smallest change, nothing existing moves. Cost: the family now has two spellings for the same concept (srdcheck/dmcheck/table-kit use 3, charactercheck uses 4), so an agent cannot learn one rule. FAMILY.md would need to record it.
B. Could-not-retrieve 3 → 4, usage errors → 3. Aligns the family on one meaning for exit 3. Cost: breaking for anyone handling exit 3 as retrieval failure, which SKILL.md has documented since 0.5.1 and which the action field is built around.
C. Leave it, and record the divergence honestly in FAMILY.md — as v2.1 already does for the adjacent class. Cost: the defect stays real. An agent that mis-invokes charactercheck still gets the honest lane's code and, per SKILL.md, escalates its own bad call to a human.
Recommendation
A. The breakage in B is real and lands on the one lane charactercheck has invested in (action, doctor, retrieval recovery), to buy consistency in a code agents read from each tool's own SKILL.md anyway — clause 1 explicitly says "read that, not this table". C leaves a known defect standing when a cheap fix exists.
A is the only option where nothing already-documented changes meaning.
This is a product-direction call, so I am not making it unilaterally. Whichever way it goes, the fix itself is small — the pattern is UsageError caught before the general handler, exactly as chaoz23/table-kit#30 did.
Until then this stays waived in the conformance baseline, tracked by this issue.
Activity
chaoz23 commentedon Aug 2, 2026
Draft PR #32 adds the explicit
derive/report --table-evaluationsurface anddraft chaoz23/table-kit#15 proves it from cold-installed wheels.
The flag uses the shared disjoint exit lane: 0 checked clean, 1 complete with
advisories/findings, and 2 refused. It is mutually exclusive with
--brief,native behavior is unchanged when omitted, and public input/runtime failures
become typed privacy-safe shared refusals. The package also exports
project_table_evaluation(report)for hosts already using the canonical PythonAPI.
Both PRs are hosted-green, draft, self-attested, and unreleased. This is one
versioned envelope slice; it does not close the broader CLI-001 NDJSON and
contract-generation criteria.
chaoz23 commentedon Aug 18, 2026
Audit evidence for the "disjoint exits" half of this issue — two concrete, verified instances.
1. Six of eleven verbs never assign an exit code
intake,stance,qa,snapshot,seatpack, andquiznever assign to the CLI'scodevariable (charactercheck/cli.py:170-195) — theyprint(...)and fall through. Onlyderive,diff, andreportassign. So those six always exit 0, including when their projection embeds findings.This is documented and intentional —
SKILL.md:44("an exit 0 there is not a cleanliness claim") andSKILL.md:120("0.7.0: exit contract is per-verb") — so it is not an unnoticed bug, and it should not be treated as one.It is a contract conflict.
FAMILY.mdclause 1 states that 0 = pass/clean and 1 = fail/findings "in every member." A per-verb contract whereqa— the verbSKILL.mditself calls the lint pass — exits 0 while emitting findings means any agent gating on the exit code reads clean.The decision this needs: either give the projection verbs a real 0/1 verdict, or amend
FAMILY.mdclause 1 to admit per-verb contracts — and if the latter, state it at the top ofSKILL.mdrather than in a changelog line at :120, since agents read the top.2. The usage-error lane shares exit 2 with the honest lane
charactercheck derive examples/sample-character.json --for-dm→ exit 2, 0 bytes on stdout, JSON on stderr. Argparse usage errors also return 2.SKILL.mddocuments exit 2 only as "unhandled/unsupported content present (the honest lane)" and instructs agents not to retry it. So an agent that makes a malformed call gets the honest lane's exit code, no stdout, and a documented instruction to escalate to a human — when the correct response is to fix the call.srdcheck separates these: bad flags return 3, documented as "a usage/internal error (fix the call), never a verdict."
Suggested: make the exits disjoint per this issue's title, or document the usage-error lane explicitly so it is classifiable.
Evidence from a conformance audit of the check-family four against
FAMILY.mdv1, 2026-08-17. Commenting here rather than opening a duplicate, since this issue already owns the area.ci: run the family conformance gate on every PR (#41)
chaoz23 commentedon Aug 25, 2026
The other two members have now landed this fix, and charactercheck is the last one — but it is blocked on a decision, not on work.
What landed elsewhere
chaoz23/dmcheck#20 and chaoz23/table-kit#30 moved usage errors off exit 2 onto exit 3, per FAMILY.md clause 1 ("usage errors are not the honest lane and should not share its exit code; srdcheck's exit 3 is the family precedent"). Both are now conformant on this rule.
Why charactercheck cannot just copy that
Exit 3 is already taken here. FAMILY.md clause 1 records charactercheck's contract as
2= unhandled content,3= could-not-retrieve — andSKILL.md:126ties exit 3 to the 0.5.1actionfield anddoctor. So the family precedent does not fit, and the code has to be chosen.The options
A. Usage errors → exit 4. Smallest change, nothing existing moves. Cost: the family now has two spellings for the same concept (srdcheck/dmcheck/table-kit use 3, charactercheck uses 4), so an agent cannot learn one rule. FAMILY.md would need to record it.
B. Could-not-retrieve 3 → 4, usage errors → 3. Aligns the family on one meaning for exit 3. Cost: breaking for anyone handling exit 3 as retrieval failure, which
SKILL.mdhas documented since 0.5.1 and which theactionfield is built around.C. Leave it, and record the divergence honestly in FAMILY.md — as v2.1 already does for the
adjacentclass. Cost: the defect stays real. An agent that mis-invokes charactercheck still gets the honest lane's code and, per SKILL.md, escalates its own bad call to a human.Recommendation
A. The breakage in B is real and lands on the one lane charactercheck has invested in (
action,doctor, retrieval recovery), to buy consistency in a code agents read from each tool's own SKILL.md anyway — clause 1 explicitly says "read that, not this table". C leaves a known defect standing when a cheap fix exists.A is the only option where nothing already-documented changes meaning.
This is a product-direction call, so I am not making it unilaterally. Whichever way it goes, the fix itself is small — the pattern is
UsageErrorcaught before the general handler, exactly as chaoz23/table-kit#30 did.Until then this stays waived in the conformance baseline, tracked by this issue.
chore: drop charactercheck's HONEST_LANE_OVERLOAD waiver
feat: FAMILY.md v2.2 harmonises exit 3; gate can see codes above 3 (#89)