Exit codes
Every code this tool can exit with, and what causes it.
Every usage mistake, refusal, and engine failure has its own exit code — never a generic non-zero
“something went wrong.” With several inputs, the process exits with the first failure’s code,
in input order; the --json envelope still carries every file’s own
exitCode, so a script processing a batch doesn’t have to guess which file caused the process exit
it observed.
This table is generated from — and kept in sync with, by a CI test — the EXIT map in this
package’s own source (packages/cli/src/exit-codes.ts). If this page and that file ever disagree,
the file is right and this page has a bug.
| Code | Name | Meaning |
|---|---|---|
| 0 | OK |
every input succeeded (or --accept-closest wrote the closest file) |
| 1 | INTERNAL |
unexpected internal error — an engine failure this CLI has no story for |
| 2 | USAGE |
unknown command/flag, a required flag missing, a flag value that failed validation, strip on webp without --reencode or on heic at all, info given --out/--in-place/--dry-run |
| 3 | NOT_FOUND |
an input path doesn’t exist, or a glob matched nothing |
| 4 | WOULD_OVERWRITE |
the computed output already exists and isn’t the input (--force, --out, or --suffix all sidestep it; --in-place is the only way to target the input itself), or the output would land on a different file this same run was also asked to read — which --force does not sidestep, see Output files |
| 10 | FIXED_DIMENSIONS |
target-unreachable: the quality floor is still over target and --no-downscale pinned the dimensions. Offered hint: allow resizing (drop --no-downscale). |
| 11 | MIN_DIMENSION |
target-unreachable: downscaling was allowed, but the next step would go below the minimum dimension the search will accept. |
| 12 | FORMAT_FLOOR |
target-unreachable: out of downscale room, or shrinking stopped buying bytes — this format’s own entropy/header floor is above the target. Offered hint: try --format webp. |
| 13 | BUDGET_EXHAUSTED |
target-unreachable: ran out of encode attempts before finding anything under target. A soft failure — nothing is structurally impossible, the search just needs a higher --max-encodes on the next run. |
| 20 | UNSUPPORTED_FORMAT |
input isn’t jpeg/png/webp/heic (a renamed SVG, GIF, BMP, or anything else); also an avif strip --reencode target, since this backend bundles no AVIF encoder |
| 21 | DECODE_FAILED |
truncated/corrupt bytes that passed magic-byte sniffing, or an empty (0-byte) file |
| 22 | INPUT_TOO_LARGE |
input exceeds the configured byte ceiling — checked against stat().size before the file is read into memory |
| 23 | DIMENSION_EXCEEDED |
decoded (or requested output) dimensions exceed the configured pixel ceiling |
| 24 | ENCODE_FAILED |
encoder failed to produce output for the requested format/quality |
| 25 | INVALID_OP |
a requested operation couldn’t be applied after clamping was already attempted (e.g. a crop rect genuinely outside the source’s bounds) |
10–13 are one family: target-unreachable
Codes 10 through 13 all come from the same engine error, target-unreachable — they’re not four
unrelated failures, they’re the four reasons the byte-target search can give up, each carrying its
own most-useful next step. Per the project’s own framing: this is a negotiation, not a crash.
The stderr sentence for any of these four names both the closest size the search actually reached
and the specific flag that would help — never a generic “couldn’t compress this.”
--accept-closest turns any of the four into exit 0: it writes the closest file the search found
instead of failing, and says so on stderr.
Only compress always has a byte target and so can hit any of 10–13 on every run. resize and
convert can hit them only when --to was given. crop, rotate, strip, and info never take
a --to flag and can never produce these four codes at all.
Codes that exist but this CLI can never produce
The engine’s ErrorCode type also includes encode-format-mismatch and no-canvas-surface — both
are canvas-backend-only failures (a browser <canvas> silently returning the wrong blob.type; no
OffscreenCanvas/document available to draw on). This CLI runs entirely on the wasm backend, not
the canvas one, so neither can ever occur here; there is deliberately no exit code reserved for
either. An engine failure this CLI genuinely has no story for falls back to 1 (INTERNAL),
never a fabricated specific-looking number.