Unify not_found CLI exit code to 2 across commands #49

Closed
opened 2026-09-22 21:57:09 +00:00 by yuris · 0 comments
Owner

Context

Two merged/in-flight slices give the same error class different CLI exit codes:

Command Error HTTP CLI exit
life item get <unknown> (#9) not_found 404 1
life operation get <unknown> (#6) not_found 404 2

The two issue specs disagreed: #9's acceptance criteria said "not_found (HTTP 404, CLI exit 1)", #6's said "If 404: exit 2 with error code not_found".

Resolution

docs/technical/cli-api.md defines exit 2 as "Invalid input/configuration" and exit 1 as "Other failure, including authentication/authorization". A caller naming an ID that does not exist is a caller input error, not an infrastructure failure, so exit 2 is correct and life item get's exit 1 is the outlier.

What to build

  • Make not_found resolve to exit 2 consistently. The cleanest cut is at the source: ApiFailure::not_found currently sets exit_code: GENERAL_ERROR; it should carry INVALID_INPUT so every route agrees, with the CLI's per-command override in life operation get (operation_exit_code) removed once it is redundant.
  • Cover both commands: unknown item id and unknown operation id both exit 2, HTTP 404, error code not_found, unchanged envelope shape.
  • Check the same question for any other route that can return not_found so the mapping is uniform.

Acceptance criteria

  • life item get <unknown-id> --json exits 2 (was 1), HTTP 404, error.code == "not_found".
  • life operation get <unknown-id> --json still exits 2.
  • No other command's exit code changes; the full suite stays green.

Notes

  • Reviewed by an independent reviewer agent, not the coordinator. Its first verdict was REQUEST CHANGES: after the per-command override was trimmed, the prepared operation state was only asserted at the HTTP envelope and never through life operation get, leaving the CLI state matrix unpinned for exactly the mapping the override had protected. Fixed by extending the existing seed_prepared_create fixture with a real CLI invocation (tests/crash_recovery_tests.rs::operation_get_exits_six_for_a_durably_prepared_operation); re-review approved at head 89551bf.
  • The reviewer also found one shape the uniformity claim must exempt, which the author had already pinned during its own audit: POST /v1/replans answers HTTP 200 status: partial with error.code: not_found, and because the status is the semantic outcome it decides before the error code, that envelope exits 6 rather than 2.
  • Audit result: eleven envelope surfaces can answer not_found (items, operations, proposals, lifecycle, update, plans, context add/search, scoped preflight, router fallback); all now exit 2. Two further not_found strings (CalendarError::error_code(), HindsightError::NotFound) were checked and are not response surfaces.
  • Suite count wording: 747 passed / 0 failed across 43 test result lines (41 test binaries plus lib/main/doc result lines).

Blocked by

None — small, self-contained; can ride with the next slice that touches src/api/mod.rs or src/cli.rs.

## Context Two merged/in-flight slices give the same error class different CLI exit codes: | Command | Error | HTTP | CLI exit | |---|---|---|---| | `life item get <unknown>` (#9) | `not_found` | 404 | **1** | | `life operation get <unknown>` (#6) | `not_found` | 404 | **2** | The two issue specs disagreed: #9's acceptance criteria said "`not_found` (HTTP 404, CLI exit 1)", #6's said "If 404: exit 2 with error code `not_found`". ## Resolution `docs/technical/cli-api.md` defines exit **2** as "Invalid input/configuration" and exit **1** as "Other failure, including authentication/authorization". A caller naming an ID that does not exist is a caller input error, not an infrastructure failure, so **exit 2 is correct** and `life item get`'s exit 1 is the outlier. ## What to build - Make `not_found` resolve to exit 2 consistently. The cleanest cut is at the source: `ApiFailure::not_found` currently sets `exit_code: GENERAL_ERROR`; it should carry `INVALID_INPUT` so every route agrees, with the CLI's per-command override in `life operation get` (`operation_exit_code`) removed once it is redundant. - Cover both commands: unknown item id and unknown operation id both exit 2, HTTP 404, error code `not_found`, unchanged envelope shape. - Check the same question for any other route that can return `not_found` so the mapping is uniform. ## Acceptance criteria - [x] `life item get <unknown-id> --json` exits 2 (was 1), HTTP 404, `error.code == "not_found"`. - [x] `life operation get <unknown-id> --json` still exits 2. - [x] No other command's exit code changes; the full suite stays green. ## Notes - Reviewed by an independent reviewer agent, not the coordinator. Its first verdict was REQUEST CHANGES: after the per-command override was trimmed, the `prepared` operation state was only asserted at the HTTP envelope and never through `life operation get`, leaving the CLI state matrix unpinned for exactly the mapping the override had protected. Fixed by extending the existing `seed_prepared_create` fixture with a real CLI invocation (`tests/crash_recovery_tests.rs::operation_get_exits_six_for_a_durably_prepared_operation`); re-review approved at head `89551bf`. - The reviewer also found one shape the uniformity claim must exempt, which the author had already pinned during its own audit: `POST /v1/replans` answers HTTP 200 `status: partial` with `error.code: not_found`, and because the status is the semantic outcome it decides before the error code, that envelope exits 6 rather than 2. - Audit result: eleven envelope surfaces can answer `not_found` (items, operations, proposals, lifecycle, update, plans, context add/search, scoped preflight, router fallback); all now exit 2. Two further `not_found` strings (`CalendarError::error_code()`, `HindsightError::NotFound`) were checked and are not response surfaces. - Suite count wording: 747 passed / 0 failed across 43 `test result` lines (41 test binaries plus lib/main/doc result lines). ## Blocked by None — small, self-contained; can ride with the next slice that touches `src/api/mod.rs` or `src/cli.rs`.
yuris closed this issue 2026-09-23 05:11:33 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/life-service#49
No description provided.