Skip to content

feat(exit-codes): Unix-standard process exit codes for all error types#564

Merged
jackwener merged 3 commits intomainfrom
feat/unix-exit-codes
Mar 28, 2026
Merged

feat(exit-codes): Unix-standard process exit codes for all error types#564
jackwener merged 3 commits intomainfrom
feat/unix-exit-codes

Conversation

@jackwener
Copy link
Copy Markdown
Owner

Summary

opencli now returns meaningful Unix process exit codes, making it a proper citizen of shell pipelines and CI scripts.

Exit code Meaning Error type
0 Success
1 Generic / unexpected error CliError, unknown errors
2 Argument / usage error ArgumentError
66 No result / not found EmptyResultError, SelectorError, untyped not-found errors
69 Service unavailable BrowserConnectError, AdapterLoadError
77 Auth required / permission denied AuthRequiredError, untyped auth errors
78 Configuration error ConfigError
124 Timeout TimeoutError
130 Interrupted (Ctrl-C) unchanged, tui.ts SIGINT handler

Implementation

  • src/errors.ts: Added EXIT_CODES constant table (single source of truth, documented in JSDoc). Added exitCode: ExitCode field to CliError base class — each subclass passes its code via super(), so there's no external mapping to maintain.
  • src/commanderAdapter.ts: Added resolveExitCode(err) that reads err.exitCode for CliError instances, and falls back to pattern-matching message text for untyped adapter errors (auth pattern → 77, not-found → 66, else → 1). The catch block now calls resolveExitCode instead of hardcoding 1.

Usage examples

# Detect auth failure vs browser not running
opencli github issues 2>/dev/null
case $? in
  0)   echo "ok" ;;
  69)  echo "start the daemon first" ;;
  77)  opencli github auth && opencli github issues ;;
  *)   echo "error $?" ;;
esac

# CI: fail fast on bad args, ignore empty results
opencli spotify search "$(cat query.txt)" || [ $? -eq 66 ]

Test plan

  • opencli spotify auth without credentials → exit 78 (ConfigError)
  • opencli github issues --bad-arg → exit 2 (ArgumentError)
  • Run with no daemon running → exit 69 (BrowserConnectError)
  • Normal successful command → exit 0

Introduce EXIT_CODES constant table (sysexits.h conventions) and wire
exitCode into every CliError subclass so the process exit code reflects
the semantic type of failure:

  0   success (default)
  1   generic / unexpected error
  2   argument / usage error        (ArgumentError)
 66   empty result / not found      (EmptyResultError, SelectorError)
 69   service unavailable           (BrowserConnectError, AdapterLoadError)
 77   permission / auth required    (AuthRequiredError)
 78   configuration error           (ConfigError)
124   timeout                       (TimeoutError)
130   Ctrl-C / SIGINT               (unchanged, tui.ts)

resolveExitCode() in commanderAdapter.ts reads err.exitCode for typed
CliErrors, and falls back to pattern-matching message text for untyped
adapter errors (auth pattern → 77, not-found pattern → 66, else → 1).

Shell scripts can now distinguish error categories:
  opencli spotify status || echo "exit $?"   # 69 if browser not running
  opencli github issues --repo x 2>/dev/null; [ $? -eq 77 ] && opencli github auth
- TIMEOUT: change from 124 → 75 (EX_TEMPFAIL); 124 is bash timeout(1)'s
  own exit code, creating ambiguity when shell runs `timeout 30 opencli`
- SelectorError: change from EMPTY_RESULT(66) → GENERIC_ERROR(1); a
  missing DOM selector is an adapter bug, not a user "no data" condition
- normalizeArgValue: throw ArgumentError instead of bare CliError so
  invalid bool args correctly exit with USAGE_ERROR(2) not GENERIC_ERROR(1)
- resolveExitCode: explicitly map 'http' classification to GENERIC_ERROR
  to keep exit-code path in sync with the render path
- tui.ts: replace hardcoded process.exit(130) with EXIT_CODES.INTERRUPTED
…constants

Extend the exit code system to cover every process exit point in the codebase.
No magic numbers remain — all exit codes are now referenced by name.

Semantic upgrades beyond pure renaming:
- plugin update missing args  → USAGE_ERROR (2) instead of 1
- plugin update conflicting   → USAGE_ERROR (2) instead of 1
- opencli install <unknown>   → USAGE_ERROR (2) instead of 1
- unknown command fallback    → USAGE_ERROR (2) instead of 1
- record with no candidates   → EMPTY_RESULT (66) instead of 1
- external CLI install fail   → SERVICE_UNAVAIL (69) instead of 1
- daemon EADDRINUSE           → SERVICE_UNAVAIL (69) instead of 1

Files touched: cli.ts, external.ts, daemon.ts, main.ts,
               clis/antigravity/serve.ts
@jackwener jackwener merged commit ab0af2d into main Mar 28, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant