Follow-up to #42, filing a classification question that PR deliberately did not decide.
#42 harmonised exit 3 = usage error across the family and moved the typed errors.py lane to 4. The line drawn was malformed invocation — argparse errors and the structured bad_flag guards go to 3; everything errors.py raises stays together on 4.
That line is defensible but not obviously the right one. Several of the ~25 kinds in PUBLIC_ERROR_KINDS look like malformed calls rather than failures:
| kind |
arguably |
bad_ref — "the reference is not one of the supported input shapes" |
the caller passed something wrong → 3 |
persona_requires_local |
a flag/input combination the caller chose → 3 |
local_files_disabled |
policy the caller can change → 3 |
missing_file |
caller pointed at a path that is not there → 3? |
not_public, not_found, network, rate_limited, upstream |
genuine retrieval failure → 4 |
input_too_large, input_too_deep, cyclic_reference, snapshot_* |
malformed input rather than a malformed call → unclear |
It was left alone in #42 because errors.py is deliberately built as one typed lane carrying an action field — its module docstring is explicit that the design point is a single distinct lane, not a taxonomy of lanes — and because reclassifying ~25 kinds in passing risked getting several wrong while shipping a breaking change.
The defect #18 reported is closed either way: no usage error shares exit 2 with the honest lane, so nothing mis-escalates to a human. This is about precision, not correctness.
Worth noting the counter-argument: the action field already tells the caller what to do, so an agent does not strictly need the exit code to distinguish "you called it wrong" from "I could not fetch it". Splitting the lane may buy little and cost a second breaking change. A reasonable outcome here is won't-fix with the reasoning recorded.
Not detectable by the conformance gate — its probe is an unknown flag, already on 3.
Sibling: chaoz23/table-kit#31 is the same shape (missing required flags still exit 2 there).
Follow-up to #42, filing a classification question that PR deliberately did not decide.
#42 harmonised exit 3 = usage error across the family and moved the typed
errors.pylane to 4. The line drawn was malformed invocation — argparse errors and the structuredbad_flagguards go to 3; everythingerrors.pyraises stays together on 4.That line is defensible but not obviously the right one. Several of the ~25 kinds in
PUBLIC_ERROR_KINDSlook like malformed calls rather than failures:bad_ref— "the reference is not one of the supported input shapes"persona_requires_locallocal_files_disabledmissing_filenot_public,not_found,network,rate_limited,upstreaminput_too_large,input_too_deep,cyclic_reference,snapshot_*It was left alone in #42 because
errors.pyis deliberately built as one typed lane carrying anactionfield — its module docstring is explicit that the design point is a single distinct lane, not a taxonomy of lanes — and because reclassifying ~25 kinds in passing risked getting several wrong while shipping a breaking change.The defect #18 reported is closed either way: no usage error shares exit 2 with the honest lane, so nothing mis-escalates to a human. This is about precision, not correctness.
Worth noting the counter-argument: the
actionfield already tells the caller what to do, so an agent does not strictly need the exit code to distinguish "you called it wrong" from "I could not fetch it". Splitting the lane may buy little and cost a second breaking change. A reasonable outcome here is won't-fix with the reasoning recorded.Not detectable by the conformance gate — its probe is an unknown flag, already on 3.
Sibling: chaoz23/table-kit#31 is the same shape (missing required flags still exit 2 there).