Skip to content

Errors & Exit Codes

indx fails loud. Every error it raises is a typed exception with an actionable message — what failed, on which file or stage, and what to do about it. This page is the complete reference for the exception hierarchy, the per-item vs fatal distinction, the StageError model, and the CLI exit codes.

indx never swallows an error or returns a half-built result silently. A core design rule is to fail loud, with context: instead of a bare ValueError or an empty except, indx raises a typed IndxError subclass whose message states what failed, where (file/stage), and the fix.

❌ raise ValueError("bad input")
✅ raise ParseError(
"Docling could not parse policies/scan.pdf: encrypted PDF. "
"Try --parser plaintext or remove the password."
)

Because every indx exception descends from a single base, downstream callers can catch the whole library cleanly with one except IndxError, or narrow to a specific subtype.

Everything indx raises descends from IndxError:

IndxError (base)
├─ ConfigError # invalid / contradictory configuration
├─ MissingDependencyError # optional extra not installed (carries the pip hint)
├─ StageError # a stage failed (carries stage name + offending path)
├─ ParseError / EmbedError / StoreError # component-level failures
└─ …
ExceptionRaised whenCarries
IndxErrorBase type — catch this to catch everything indx raises.
ConfigErrorConfiguration is invalid or contradictory (bad indx.toml, unknown component name, mutually exclusive options).The offending key/value and the valid options.
MissingDependencyErrorAn optional backend is selected but its extra is not installed.The exact pip install indx[<extra>] hint.
StageErrorA stage fails (per-item or fatal).The stage name and the offending item path.
ParseErrorA Parser cannot produce a ParsedDoc for a file.The file path and the reason; often a suggested alternate parser.
EmbedErrorAn Embedder fails to vectorize text.The failing batch/chunk context.
StoreErrorA Store cannot upsert, query, or persist.The backend and the failing operation.

PipelineError is the fatal wrapper: when a stage aborts, the pipeline re-raises a PipelineError that wraps the underlying StageError (see below).

indx draws a sharp line between a single bad file and a broken run. This is what lets a 10,000-file directory finish even when a handful of PDFs are corrupt.

Both kinds are described by one Pydantic model, StageError:

class StageError(BaseModel):
stage: str
item: Optional[str] = None # e.g. the file path or chunk id
kind: Literal["skip", "fatal"]
message: str
detail: Optional[str] = None
FieldMeaning
stageThe stage name that produced the error, e.g. "parse", "enrich".
itemThe offending item — a file path or chunk id. None for whole-stage failures.
kind"skip" (per-item, recoverable) or "fatal" (aborts the run).
messageShort, actionable summary of what went wrong.
detailOptional longer context (e.g. an upstream exception summary).

A single file fails to parse, or one document’s LLM enrichment call times out. The item is skipped, a StageError(kind="skip") is appended to ctx.errors, and the pipeline continues with the remaining items.

skip is the default behaviour for the Parse (02) and Enrich (05) stages — the two stages most exposed to messy real-world inputs and flaky model calls. See Parse and Enrich for stage-specific behaviour.

indx ./docs → ./ai-ready
02 parse 126 ok, 2 skipped

A fatal error means the run cannot meaningfully continue:

  • misconfiguration (ConfigError),
  • an unresolvable component name (an unknown --store, --parser, etc.),
  • an unreachable store or other infrastructure failure,
  • a stage raising an unhandled exception.

On a fatal error the pipeline aborts and re-raises a PipelineError wrapping the StageError. Nothing is half-sealed.

Per-item failures are not lost — they accumulate on ctx.errors during the run and are surfaced on the resulting space under space.metadata["errors"]:

space = DirectoryPipeline().run("./docs", "./ai-ready")
for err in space.metadata["errors"]:
print(err["stage"], err["item"], err["message"])

You can inspect them after the fact with indx inspect as well. Skipped items are also reflected in the per-stage ok / skipped counts on stdout.

When you want a clean-or-nothing build (CI, reproducible releases), pass --strict on the CLI or strict=True to the SDK. This promotes every skip to fatal — the first per-item failure aborts the run with a PipelineError.

Terminal window
indx ./docs --out ./ai-ready --strict
space = DirectoryPipeline(strict=True).run("./docs", "./ai-ready")

A strict-mode abort exits with code 1 (see below). Use it when a partial knowledge space would be worse than no knowledge space.

The CLI maps outcomes to stable POSIX-friendly exit codes so scripts and CI can branch on them. Successful builds return 0; everything else is non-zero.

CodeMeaning
0Success.
1Fatal pipeline/runtime error (includes a --strict skip-turned-fatal).
2Usage error (bad flags or arguments).
3Configuration error (invalid indx.toml / unknown component name).
4Archive error (missing, corrupt, or incompatible .indx).

A few mappings worth calling out:

  • A ConfigError (including an unknown --store/--parser/--embedder name) exits 3, not 1 — configuration problems are distinguished from runtime failures.
  • A failed indx inspect or indx query against a missing or incompatible archive exits 4. A major indx_version mismatch on load is a fatal, clearly-messaged archive error.
  • In strict mode, a per-item failure that would otherwise be a recorded skip becomes a fatal error and exits 1.
Terminal window
indx ./docs --out ./ai-ready --strict
if [ $? -ne 0 ]; then
echo "build failed — see errors above"
fi

See the full CLI reference for the flags that influence these outcomes (--strict, --config, --store, and friends).

indx is local-first and treats credentials as radioactive. API keys, tokens, and connection strings are redacted in logs, error messages, and serialized output. When configuration is logged for diagnostics, indx logs the config shape, not its secret values. No error or index.json snapshot will ever echo a key back to you.

Catch the base type to handle any indx failure, or narrow to a specific subtype:

from indx import DirectoryPipeline
from indx.core.errors import (
IndxError, ConfigError, MissingDependencyError, PipelineError,
)
try:
space = DirectoryPipeline(store="qdrant").run("./docs", "./ai-ready")
except MissingDependencyError as exc:
print("Install a backend:", exc) # carries the pip hint
except ConfigError as exc:
print("Fix your config:", exc)
except PipelineError as exc:
print("Run aborted:", exc) # wraps the underlying StageError
except IndxError as exc:
print("indx failed:", exc) # catch-all for the library

For the broader API surface these errors flow through, see the SDK reference, the component protocols, and the data models.