Fifty-seven sites reported any error from a store or helper call with ExitUsage, so a SQLite I/O failure was exit 2 over ssh and HTTP 400 over the API, and a not-found from the store was a usage error too.
Ctx.failErr maps store.ErrNotFound (wrapped or not) to exit 3, a database or I/O failure to exit 1 via store.IsInternal (a SQLite driver error, a path error, a closed transaction or connection, a cancelled context), and everything else, which is the caller's input or a state that refuses the change, to exit 2 as before. Every site was converted mechanically; the sentinels ErrExists, ErrDuplicateKey and ErrLastAdmin keep exit 2.
TestFailErrExitCodes and TestIsInternal. The exit-code-sensitive e2e tests (issues, MRs, repos, orgs, teams, releases, milestones, webhooks, tokens, admin, deploy keys, mirrors, import, labels, profiles, pages) pass.
Stacked on !203; no code dependency.
Closes #107
retargeted from json-first to main: !203 merged
2026-09-04 00:10 UTC