# Exit codes (/docs/reference/exit-codes)



LPM CLI follows the conventional Unix exit-code convention: `0` for success, non-zero for failure. The CLI is intentionally narrow about which non-zero codes it produces — most failures exit `1`. A few specific scenarios warrant a different code.

| Code          | Meaning                                                                                                                                                                                                                                |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`           | Success. The command completed without error.                                                                                                                                                                                          |
| `1`           | General error. Network failure, registry error, parse failure, validation failure, missing dependency, audit found vulnerabilities, etc. The error message on stderr (or in `--json` output) describes the specific cause.             |
| `2`           | Usage error. `lpm` was invoked with no subcommand, or with arguments clap couldn't parse. The CLI's help text is printed before exit.                                                                                                  |
| *passthrough* | Forwarded subprocess exit code. Commands that wrap external processes (`lpm run`, `lpm <file>`, `lpm exec`, `lpm dlx` / `lpx`, `lpm test`, `lpm bench`, `lpm lint`, `lpm fmt`, `lpm check`) exit with the wrapped command's exit code. |

## Subprocess passthrough [#subprocess-passthrough]

When a wrapped tool exits non-zero — your test suite fails, your script crashes, oxlint reports an error — LPM CLI forwards the exit code so CI gates see the right signal:

```bash
lpm test
echo "tests exit code: $?"     # whatever vitest / jest exited with
```

This is implemented via `LpmError::ExitCode(code)` flowing up to the top of `main`, which calls `std::process::exit(code)`. No code mangling — `lpm test` exiting `1` from vitest looks identical to running vitest directly.

## In `--json` mode [#in---json-mode]

With `--json`, every error path also emits a structured JSON object on stdout before exiting:

```json
{
  "schema_version": 1,
  "success": false,
  "error": "registry: not found",
  "error_code": "not_found"
}
```

The `error_code` field is the canonical short name for the error variant. Use it for programmatic dispatch in CI / agents (the `error` message text is human-facing and subject to change; `error_code` is the stable contract).

`schema_version` identifies the JSON document shape. On top-level error envelopes, version `1` is the shared error-envelope schema. Command success envelopes may also expose `schema_version`; those versions are command-specific.

When LPM CLI can suggest a concrete remediation command, the envelope includes `next_steps`:

```json
{
  "schema_version": 1,
  "success": false,
  "error": "authentication required",
  "error_code": "auth_required",
  "next_steps": [
    {
      "description": "Authenticate with the LPM.dev Registry",
      "command": "lpm login"
    }
  ]
}
```

`next_steps` is optional and only appears when LPM CLI has a safe command to hand to a human, CI job, or agent.

Most errors put a human-facing string in `error`; rich diagnostics may use an object with a `message` field plus machine-readable context. Resolver failures use `error_code: "resolution_failed"` and include `package`, `requested`, `required_by`, `kind`, and candidate-summary fields; see [Resolver failures](/docs/packages/resolver#resolution-failures).

### Full `error_code` catalog [#full-error_code-catalog]

| `error_code`                 | Emitted when                                                                                                                                        |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `auth_required`              | Operation needs an authenticated session and none is present                                                                                        |
| `session_expired`            | A stored session token has expired and self-heal didn't recover                                                                                     |
| `forbidden`                  | The server accepted the token but rejected the operation (plan gate, ACL, etc.)                                                                     |
| `not_found`                  | Package, version, vault, or other addressable resource doesn't exist                                                                                |
| `rate_limited`               | Server (or a downstream like GitHub) returned a rate-limit response                                                                                 |
| `network`                    | Underlying network failure (DNS, TCP, TLS) before the server replied                                                                                |
| `http`                       | Server replied but the response was malformed or an unhandled HTTP status                                                                           |
| `registry`                   | Registry-side error not covered by a more specific code                                                                                             |
| `resolution_failed`          | Dependency resolution could not find a compatible package version, platform, policy-allowed candidate, or complete solution graph                   |
| `typosquat_suspected`        | A new direct package name looks like a popular package and is not allow-listed in `lpm.toml`                                                        |
| `invalid_package_name`       | Argument doesn't match the package-name grammar (e.g., bad scope, invalid chars)                                                                    |
| `invalid_integrity`          | An SRI string is structurally malformed                                                                                                             |
| `integrity_mismatch`         | A downloaded tarball's hash differs from the lockfile / manifest expectation                                                                        |
| `invalid_version`            | Argument isn't a valid semver                                                                                                                       |
| `invalid_version_range`      | Argument isn't a valid semver range                                                                                                                 |
| `engine_mismatch`            | A workspace-root `engines.lpm` / `engines.node` or required dependency `engines.node` constraint isn't satisfied                                    |
| `script`                     | A lifecycle script (or `lpm run` script body) failed (script field also covers `ScriptWithOutput`)                                                  |
| `task`                       | Task-runner failure (graph cycle, missing dep, cache corruption)                                                                                    |
| `workspace`                  | Workspace-config error (bad glob, member resolution, schema)                                                                                        |
| `env_validation`             | `envSchema` validation in `lpm.json` failed (missing required var, format mismatch)                                                                 |
| `plugin`                     | Managed plugin tool (Oxlint, Biome, Rolldown) failed to download, verify, or launch                                                                 |
| `cert`                       | mkcert / cert install error from `lpm cert`                                                                                                         |
| `tunnel`                     | Tunnel relay / domain claim / WebSocket failure                                                                                                     |
| `store`                      | Content-addressable store error (corruption, missing object, write failure)                                                                         |
| `io`                         | Filesystem I/O failed below a higher-level operation                                                                                                |
| `json`                       | JSON parse / serialize failure on a file or response body                                                                                           |
| `security_floor`             | A higher-authority floor or managed policy refused a weaker security posture                                                                        |
| `security_approval_store`    | Local signed security state could not be verified; run `lpm security repair` to quarantine stale state                                              |
| `security_approval_required` | A guarded weakening needs explicit approval before LPM CLI will proceed                                                                             |
| `self_update_paused`         | Self-update was administratively paused for the resolved release channel                                                                            |
| `self_update_rate_limited`   | Self-update hit a per-IP probe rate limit                                                                                                           |
| `exit_code`                  | Subprocess passthrough — the exit code came from a wrapped tool (vitest, oxlint, etc.); see [Subprocess passthrough](#subprocess-passthrough) above |

For single-package subprocess passthrough (`lpm test`, `lpm bench`, single-package `lpm lint` / `fmt` / `check`), `--json` preserves the wrapped command's own stdout — LPM CLI does not wrap it. This keeps the "single JSON result" contract for tools that already emit JSON. `lpm run --json` is different: it emits an LPM CLI task metadata envelope even for one script.

Workspace and multi-task aggregation DOES emit an LPM CLI envelope on stdout, but the envelope shape varies by command:

* **`lpm lint` / `fmt` / `check` / `test` / `bench` workspace mode** (`--all` / `--filter` / `--affected`) capture each member's subprocess stdout/stderr and surface them inside the envelope only on failure (truncated at 10 MB). Spawn / config / plugin / detection failures appear as `exit_code: null` paired with an `error` string, distinguishing "ran and exited non-zero" from "couldn't even launch." For `test` / `bench`, a member with no installed runner (no vitest/jest/mocha for `test`, no vitest or `scripts.bench` for `bench`) shows up as a per-member detection failure rather than aborting the entire run.
* **`lpm run`** emits a metadata-only envelope for single-script, multi-script, and workspace runs — each task/member's status, exit code, duration, cache hit, skip reason — but never the captured subprocess stdout/stderr. If a workspace filter matches no packages and `--fail-if-no-match` is not set, JSON mode emits a successful zero-package envelope. To debug a failing member, re-run that member without `--json`.

## Common scenarios [#common-scenarios]

| Scenario                                                                        | Exit code                                                |
| ------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `lpm install` succeeds                                                          | `0`                                                      |
| `lpm install` finds an unresolvable dep                                         | `1`                                                      |
| `lpm install --json` finds an unresolvable dep                                  | `1` (and `error_code: "resolution_failed"`)              |
| `lpm install axois --json` hits the typosquat guard                             | `1` (and `error_code: "typosquat_suspected"`)            |
| `lpm audit` finds a vulnerability under the default `--fail-on=all`             | `1`                                                      |
| `lpm audit` finds nothing (or a finding excluded by `--fail-on`)                | `0`                                                      |
| `lpm audit` cannot complete its OSV or LPM.dev Registry vulnerability lookup    | `1` (never reported as a clean audit)                    |
| `lpm audit --level severe` uses an unknown severity                             | `2` (usage error)                                        |
| `lpm tidy` finds unused dependencies or phantom imports                         | `1`                                                      |
| `lpm tidy --fix` removes all fixable unused dependencies and no findings remain | `0`                                                      |
| `lpm doctor` finds an issue                                                     | `1`                                                      |
| `lpm doctor` finds nothing                                                      | `0`                                                      |
| `lpm test` runs vitest, vitest exits 0                                          | `0`                                                      |
| `lpm test` runs vitest, vitest exits 1                                          | `1` (forwarded)                                          |
| `lpm install --no-sandbox --json` without approval                              | `1` (and `error_code: "security_approval_required"`)     |
| A managed policy blocks `lpm config sandbox --set none`                         | `1` (and `error_code: "security_floor"`)                 |
| `lpm` (no args)                                                                 | `2`                                                      |
| `lpm install --frobnicate` (unknown flag)                                       | `2`                                                      |
| `lpm install` blocked by missing auth                                           | `1` (and `error_code: "auth_required"` in `--json` mode) |

## CI gating idioms [#ci-gating-idioms]

```yaml title=".github/workflows/ci.yml (excerpt)"
- run: lpm install --offline --strict-integrity
- run: lpm audit --fail-on vuln
- run: lpm test
- run: lpm lint
- run: lpm fmt --check
- run: lpm check
```

Each step passes / fails on its own exit code. No special handling required.

## See also [#see-also]

* [`lpm audit --fail-on`](/docs/packages/audit) — gate exit code by finding type
* [`lpm fmt --check`](/docs/dev/fmt) — exit non-zero on unformatted files
* [Global flags](/docs/commands#global-flags) — `--json` for structured failure output
