Libra CLI Error Codes

July 2, 2026 · View on GitHub

Libra now exposes failures through a stable three-layer contract:

  1. exit code Fast shell/CI branching. 0 means success. Any non-zero value is a failure.
  2. stable error code A machine-stable identifier for agents, wrappers, and higher-level UX.
  3. structured JSON report When structured mode is enabled, the last stderr line is JSON and carries category, message, hints, and details.

This contract is implemented in src/utils/error.rs.

Output Contract

On failure, Libra always writes a human-readable error block to stderr.

When stderr is not a TTY, Libra additionally writes:

  1. An Error-Code: ... line
  2. A final JSON line with the structured report

This keeps interactive terminal output readable while preserving structured data for shell pipelines, CI, and wrappers that capture stderr.

To force structured output even in an interactive terminal, set:

LIBRA_ERROR_JSON=1

Example:

fatal: not a libra repository (or any of the parent directories): .libra
Error-Code: LBR-REPO-001
Hint: run 'libra init' to create a repository in the current directory.
{"ok":false,"error_code":"LBR-REPO-001","category":"repo","exit_code":128,"severity":"fatal","message":"not a libra repository (or any of the parent directories): .libra","hints":["run 'libra init' to create a repository in the current directory."]}

Warnings and progress messages remain plain text. Only failures participate in this contract.

Status-only probes are an explicit exception. libra cat-file -e preserves Git-compatible silent 0/1 behavior and does not emit the human-readable block or trailing JSON report when the object is missing.

Exit Codes

ExitMeaningPrimary automation use
0SuccessContinue
9Warnings emitted (--exit-code-on-warning)Review warnings
128Fatal runtime errorCheck error_code for category
129Usage / invalid targetFix CLI invocation

Set LIBRA_FINE_EXIT_CODES=1 to re-enable the legacy fine-grained exit codes (2-8) described in the migration section below. When this variable is unset or 0, Libra uses the Git-standard codes shown above.

Migration From Fine-Grained Exit Codes

Libra previously used fine-grained exit codes (2-8) to distinguish failure categories. The current default aligns with Git-standard exit codes: 128 for fatal errors, 129 for usage errors, and 9 for warnings. The stable symbolic error_code field in the JSON report continues to provide fine-grained classification.

This is an intentional migration from fine-grained exit codes (2-8) to Git-standard exit codes (128/129), improving compatibility with Git-aware tooling and CI systems.

Fine-grained behaviorFine-grained exitGit-standard contract
Usage / invalid target2129 + same LBR-CLI-* code
Fatal runtime errors (repo, conflict, network, auth, I/O, internal)3-8128 + same LBR-* code
Warnings emitted9Unchanged 9
cat-file -e missing object probe1Still 1 with no stderr output

If you have existing scripts that branch on fine-grained exit codes (2-8), you can set LIBRA_FINE_EXIT_CODES=1 to preserve the old behavior. Otherwise, migrate your scripts to branch on 128/129 and use the JSON error_code field for fine-grained classification. If your automation allocates a TTY, set LIBRA_ERROR_JSON=1 so the structured report is always present.

Complete Stable Code Table

ExitStable codeCategoryMeaningTypical examples
129LBR-CLI-001cliUnknown commandlibra wat
129LBR-CLI-002cliInvalid or missing CLI argumentsmissing required flag, conflicting flags
129LBR-CLI-003cliInvalid object, revision, pathspec, or move targetbad ref, invalid pathspec, outside-repo move target
128LBR-REPO-001repoNot inside a Libra repositoryrunning repo commands outside .libra
128LBR-REPO-002repoRepository metadata is corrupt or incompatiblemissing DB, corrupted metadata
128LBR-REPO-003repoRepository state blocks the operationno commits yet, detached state mismatch, missing configured remote
128LBR-CONFLICT-001conflictUnresolved conflict is presentmerge/rebase conflict still unresolved
128LBR-CONFLICT-002conflictOperation blocked to avoid overwriting statenon-fast-forward, destination exists, dirty worktree
128LBR-POLICY-001conflictBranch policy (protect/archive metadata) blocked the ref updatebranch reset / update-ref on a protected or archived branch
128LBR-CASE-001conflictPaths that differ only by case collide on a case-insensitive filesystemadd/checkout/switch/mv under core.casehandling=error
128LBR-NET-001networkRemote unreachable or transport unavailableDNS, timeout, TLS, connection refused
128LBR-NET-002networkProtocol, negotiation, or pack failurepacket-line, sideband, unpack/ref update protocol errors
128LBR-AUTH-001authMissing identity, token, or credentialsmissing commit identity, missing API key, missing SSH material
128LBR-AUTH-002authCredential present but permission deniedforbidden push, insufficient scope
128LBR-IO-001ioRead/open/load failurefailed to open pack, failed to read index
128LBR-IO-002ioWrite/save/update/remove failurefailed to write index, failed to remove file
128LBR-INTERNAL-001internalUnexpected internal invariant failureinvariant break, unclassified internal failure
128LBR-BISECT-001repobisect view / bisect run invoked outside an active bisect sessionrunning bisect view before bisect start
128LBR-BISECT-002internalbisect run command exited with code ≥ 128 or was killed by a signalrun script aborted via SIGINT, exit 130
128LBR-BISECT-003repobisect run cannot advance because no candidate commits remainbisect already converged when run is invoked
129LBR-ADD-001clilibra add invoked with no matched paths and nothing already stagedlibra add nonexistent.txt on an empty index
128LBR-UNSUPPORTED-001repoOperation declined because the requested mode is intentionally unsupported in this batchrequesting a Git feature explicitly declined in docs/development/commands/_compatibility.md
128LBR-AGENT-001internalAI agent run exceeded a configured budget dimension (tokens, tool calls, wall-clock, source calls, or cost)a sub-agent ran 500 tool calls when max_tool_calls = 200
9LBR-WARN-001warningCommand completed with warnings--exit-code-on-warning

Stable Codes By Category

CLI

Stable codeMeaning
LBR-CLI-001Unknown command
LBR-CLI-002Invalid or missing CLI arguments
LBR-CLI-003Invalid object, revision, pathspec, or move target
LBR-ADD-001libra add matched no paths and nothing already staged

Repository

Stable codeMeaning
LBR-REPO-001Not inside a Libra repository
LBR-REPO-002Repository metadata is corrupt or incompatible
LBR-REPO-003Repository state blocks the operation

Conflict

Stable codeMeaning
LBR-CONFLICT-001Unresolved conflict is present
LBR-CONFLICT-002Operation blocked to avoid overwriting state
LBR-POLICY-001Branch policy (protect/archive metadata) blocked the ref update.
LBR-CASE-001Paths that differ only by case collide on a case-insensitive filesystem.

Network

Stable codeMeaning
LBR-NET-001Remote unreachable / transport unavailable
LBR-NET-002Protocol, negotiation, or pack failure

Auth

Stable codeMeaning
LBR-AUTH-001Missing identity, token, or credential material
LBR-AUTH-002Credential present but permission denied

I/O

Stable codeMeaning
LBR-IO-001Read/open/load failure
LBR-IO-002Write/save/update/remove failure

Internal

Stable codeMeaning
LBR-INTERNAL-001Unexpected internal invariant failure
LBR-AGENT-001AI agent run exceeded a configured budget dimension (tokens, tool calls, wall-clock, source calls, or cost)

Unsupported

Stable codeMeaning
LBR-UNSUPPORTED-001Operation declined because the requested mode is intentionally unsupported in this batch (see docs/development/commands/_compatibility.md)

Reportable internal failures (CliError::internal, explicit InternalInvariant mappings, and legacy internal error / panic / invariant / unexpected messages) include the Libra GitHub Issues URL in human and JSON output so users and automation have a stable report destination.

Bisect

The LBR-BISECT-* codes are emitted exclusively by libra bisect and its subcommands. They use existing categories (repo for state issues, internal for run-script failures) so generic LBR-REPO-* / LBR-INTERNAL-* shell patterns continue to match them.

Stable codeCategoryMeaning
LBR-BISECT-001repobisect view or bisect run invoked outside an active bisect session
LBR-BISECT-002internalbisect run command exited with code ≥ 128 or was killed by a signal
LBR-BISECT-003repobisect run cannot advance because no candidate commits remain

Warning

Stable codeMeaning
LBR-WARN-001Command completed with warnings (--exit-code-on-warning)

How To Use Codes

Shell And CI

Use exit code for coarse branching:

if libra push; then
  echo "ok"
else
  case "$?" in
    128) echo "fatal error (check error_code for details)" ;;
    129) echo "fix CLI invocation" ;;
    9)   echo "warnings emitted" ;;
  esac
fi

For fine-grained classification, parse the JSON report from stderr:

output="$(libra push 2>&1)" || {
  json_line="$(printf '%s\n' "$output" | tail -n 1)"
  code="$(printf '%s\n' "$json_line" | jq -r '.error_code')"
  case "$code" in
    LBR-REPO-*)     echo "repository problem" ;;
    LBR-NET-*)      echo "network problem" ;;
    LBR-AUTH-*)     echo "auth problem" ;;
    LBR-CONFLICT-*) echo "conflict" ;;
    LBR-IO-*)       echo "I/O problem" ;;
    LBR-CLI-*)      echo "usage problem" ;;
    *)              echo "other: $code" ;;
  esac
}

Agents And Wrappers

Use the final stderr JSON line for precise handling. The recommended order is:

  1. Check exit_code to decide coarse recovery.
  2. Check error_code to classify the exact failure family.
  3. Use message, hints, and details to build the next user-facing prompt.

Example extraction:

stderr="$(libra add missing.txt 2>&1 >/dev/null)" || true
json_line="$(printf '%s\n' "$stderr" | tail -n 1)"
printf '%s\n' "$json_line" | jq '.error_code, .message, .hints'

If the wrapper runs Libra under a pseudo-terminal, export LIBRA_ERROR_JSON=1 to force the structured report.

Interactive Discovery

Libra exposes the table directly through help:

libra help error-codes

Alias:

libra help errors

JSON Schema

Every structured failure report includes:

FieldTypeMeaning
okboolAlways false for error reports
error_codestringStable code such as LBR-REPO-001
categorystringcli, repo, conflict, network, auth, io, internal, warning
exit_codenumberShell-facing exit code
severitystringerror or fatal
messagestringUser-facing error summary without prefix
usagestring?Optional usage text for CLI errors
hintsstring[]Optional actionable hints
detailsobjectOptional structured context

Architecture

The design has four layers:

  1. CliError Owns stable code, exit code, hints, details, and rendering.
  2. execute_safe(...) -> CliResult<()> CLI-facing command entrypoints return structured errors instead of printing ad hoc text.
  3. emit_legacy_stderr(...) Compatibility bridge for legacy commands that still produce fatal: / error: strings.
  4. main Exits with err.exit_code() and keeps success at 0.

This lets Libra migrate incrementally without breaking the stable external contract.

How To Change Codes

Stable codes are part of Libra's public CLI contract. Changing them requires compatibility discipline.

Rules

  1. Never reuse an existing stable code for a different failure meaning.
  2. Do not change an existing code's exit code or category unless the old mapping is clearly wrong and the migration is intentional.
  3. Prefer adding a new stable code over silently repurposing an existing one.
  4. Keep the human-readable message flexible, but treat error_code, category, and exit_code as stable.
  5. When heuristics classify legacy text, update the classifier so old code paths still map to the same stable contract.

Required Change Steps

When adding or changing a code:

  1. Update src/utils/error.rs: add the StableErrorCode variant, its string, category, exit-code mapping, and description.
  2. Update classification: adjust the legacy inference helpers so old fatal: / error: messages still map correctly.
  3. Update command mapping: when a command has a precise failure mode, set the stable code explicitly instead of relying only on heuristics.
  4. Update documentation: keep this file and libra help error-codes output in sync.
  5. Update tests: assert both human-readable stderr and parsed JSON fields.

Compatibility Guidance

  • Adding a new stable code is backward compatible if old codes keep their meaning.
  • Reclassifying a failure from one existing stable code to another is externally visible and should be treated like a CLI contract change.
  • If a change affects automation, wrappers, or agents, note it in release notes or migration notes.

Testing

Integration tests run Libra with captured stderr, so structured mode is enabled by default. They parse the final JSON stderr line and assert both:

  • human-readable text still makes sense
  • machine-readable fields are stable

Shared helpers live in tests/command/mod.rs.