Skip to content
Image KB Reducer
Esc
↑↓navigate↵open⌘Jpreview
On this page

Give-up codes

The four reasons a byte-target search can't reach its target, and what to offer instead.

target-unreachable is a negotiation, not a failure

When a { mode: "target" } search can’t put a file at or under the requested byte count, runJob doesn’t throw and doesn’t silently hand back an oversized file. It resolves with ok: false, and that result still carries a partial — the closest file the search actually managed to produce, over target but real and downloadable — plus suggestions: the specific levers that would make the target reachable.

{
  ok: false,
  error: { code: "target-unreachable", message: "…", reason: GiveUpCode },
  partial?: EncodedImage,   // the closest candidate found, if any
  warnings: Warning[],
  suggestions: Suggestion[],
}

error.reason is a GiveUpCode — the finer-grained “why” behind the flat target-unreachable code. It only exists for this one ErrorCode; every other failure leaves reason undefined.

The four codes

GiveUpCode Meaning Suggested lever
FIXED_DIMENSIONS The quality floor is still over target and the caller pinned dimensions (allowDownscale: false). Allow downscaling.
MIN_DIMENSION Downscaling was allowed, but the next hop would breach minDimension. Nothing left to shrink safely — try a different format, or raise the target.
FORMAT_FLOOR Out of downscale hops, or shrinking stopped buying bytes: this format’s own entropy-plus-header floor is above the target. Switch format (typically to WebP).
BUDGET_EXHAUSTED Ran out of encode attempts before finding anything under target. A soft failure — nothing is structurally impossible, the search simply needs to run longer. Call runJob again with a higher maxEncodes, or accept partial.

This is exactly the same vocabulary the CLI’s exit codes 10–13 map to one-for-one — see Exit codes if you’re comparing the two surfaces.

Suggestion

type Suggestion =
  | { kind: "allow-downscale" }
  | { kind: "switch-format"; format: ImageFormat }
  | { kind: "raise-target"; bytes: number }
  | { kind: "accept-closest"; bytes: number }
  | { kind: "lower-min-quality"; minQuality: number }
  | { kind: "keep-searching"; additionalEncodes: number }

These are exactly the levers a “allow resizing” / “try WebP” banner would flip and resubmit — the CLI’s own stderr sentences and --json suggestions array are built from this same union, worded as text instead of structured data (see --json output).

lower-min-quality is declared in the type but nothing in this milestone emits it yet — kept because widening a union must never silently drop a variant a future search change might start using. Don’t build a UI branch that assumes every kind currently fires.

Using it

const result = await runJob(job, { file: bytes, backend })

if (!result.ok && result.error.code === "target-unreachable") {
  console.log(`closest: ${result.partial?.sizeBytes} bytes`)
  for (const s of result.suggestions) {
    if (s.kind === "switch-format") console.log(`try format: ${s.format}`)
    if (s.kind === "raise-target") console.log(`raise target to: ${s.bytes}`)
  }
  // offer result.partial to the user directly -- it's a real, complete file
}

isRecoverableCode("target-unreachable") (from the root import) returns true — of every ErrorCode this engine defines, target-unreachable and dimension-exceeded are the two the package itself considers negotiable in the product sense, as opposed to a hard failure like a bad input or an encoder crash.

Was this page helpful?