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.
The fail-loud principle
Section titled “The fail-loud principle”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.
Exception hierarchy
Section titled “Exception hierarchy”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└─ …| Exception | Raised when | Carries |
|---|---|---|
IndxError | Base type — catch this to catch everything indx raises. | — |
ConfigError | Configuration is invalid or contradictory (bad indx.toml, unknown component name, mutually exclusive options). | The offending key/value and the valid options. |
MissingDependencyError | An optional backend is selected but its extra is not installed. | The exact pip install indx[<extra>] hint. |
StageError | A stage fails (per-item or fatal). | The stage name and the offending item path. |
ParseError | A Parser cannot produce a ParsedDoc for a file. | The file path and the reason; often a suggested alternate parser. |
EmbedError | An Embedder fails to vectorize text. | The failing batch/chunk context. |
StoreError | A 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).
Per-item vs fatal failures
Section titled “Per-item vs fatal failures”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.
The StageError model
Section titled “The StageError model”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| Field | Meaning |
|---|---|
stage | The stage name that produced the error, e.g. "parse", "enrich". |
item | The offending item — a file path or chunk id. None for whole-stage failures. |
kind | "skip" (per-item, recoverable) or "fatal" (aborts the run). |
message | Short, actionable summary of what went wrong. |
detail | Optional longer context (e.g. an upstream exception summary). |
skip — per-item, pipeline continues
Section titled “skip — per-item, pipeline continues”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 skippedfatal — abort and re-raise
Section titled “fatal — abort and re-raise”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.
Where skipped errors surface
Section titled “Where skipped errors surface”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.
Strict mode promotes skips to fatal
Section titled “Strict mode promotes skips to fatal”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.
indx ./docs --out ./ai-ready --strictspace = 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.
CLI exit codes
Section titled “CLI exit codes”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.
| Code | Meaning |
|---|---|
0 | Success. |
1 | Fatal pipeline/runtime error (includes a --strict skip-turned-fatal). |
2 | Usage error (bad flags or arguments). |
3 | Configuration error (invalid indx.toml / unknown component name). |
4 | Archive error (missing, corrupt, or incompatible .indx). |
A few mappings worth calling out:
- A
ConfigError(including an unknown--store/--parser/--embeddername) exits3, not1— configuration problems are distinguished from runtime failures. - A failed
indx inspectorindx queryagainst a missing or incompatible archive exits4. A majorindx_versionmismatch on load is a fatal, clearly-messaged archive error. - In strict mode, a per-item failure that would otherwise be a recorded
skipbecomes a fatal error and exits1.
indx ./docs --out ./ai-ready --strictif [ $? -ne 0 ]; then echo "build failed — see errors above"fiSee the full CLI reference for the flags that influence these outcomes (--strict, --config, --store, and friends).
Secrets are never logged
Section titled “Secrets are never logged”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.
Catching errors in the SDK
Section titled “Catching errors in the SDK”Catch the base type to handle any indx failure, or narrow to a specific subtype:
from indx import DirectoryPipelinefrom 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 hintexcept ConfigError as exc: print("Fix your config:", exc)except PipelineError as exc: print("Run aborted:", exc) # wraps the underlying StageErrorexcept IndxError as exc: print("indx failed:", exc) # catch-all for the libraryFor the broader API surface these errors flow through, see the SDK reference, the component protocols, and the data models.