Errors and exit codes¶
Failure behavior is part of Sommelier's contract. Every failure surfaces as a subclass of SommelierError with a stable error code and a stable exit code, defined in one place: sommelier/errors.py. The error code names the kind of failure; the exit code says whose fault it is, which is the thing a script or a CI job actually needs to branch on.
Message format¶
Errors print to stderr as one line, sometimes followed by a hint:
sommelier: SOM201: config file not found: examples/missing.yaml
hint: Pass --config with the path to a Sommelier YAML config.
Any exception that is not a SommelierError is caught at the CLI boundary and reported as sommelier: SOM000: unexpected error: <message> with exit 5. Passing the global --debug flag (before the subcommand) additionally prints the full Python traceback for either case. See the CLI reference.
Exit codes¶
| Exit | Whose fault | Your move |
|---|---|---|
| 0 | Nobody's | Nothing, it worked |
| 2 | Your input: flags, config, missing or malformed artifacts | Fix the input and rerun |
| 3 | Your environment: an optional dependency or external requirement is missing | Install the named extra or satisfy the requirement |
| 4 | Resources or remote execution: out of memory, dependency/provider timeouts, evaluation aborts | Inspect the config and provider boundary named in the hint, reduce work, or rent a larger GPU |
| 5 | Sommelier: a bug or a broken invariant | File an issue |
The split between 2 and 3 is deliberate: a wrong config field and a missing CUDA stack are different problems with different fixes, and a retry loop should treat them differently. Exit 5 means the pipeline detected a state that its own design says must be impossible; retrying will not help, and the maintainers want to know.
The hierarchy¶
SommelierError SOM000 exit 5
├── UserInputError SOM002 exit 2
│ ├── ConfigError SOM201 exit 2
│ ├── SchemaValidationError SOM202 exit 2
│ └── ArtifactNotFoundError SOM203 exit 2
├── ExternalDependencyError SOM003 exit 3
├── RemoteExecutionError SOM004 exit 4
│ └── ResourceError SOM401 exit 4
├── EvaluationError SOM005 exit 4
├── InvariantViolation SOM005 exit 5
└── SecurityPolicyError SOM006 exit 5
| Class | Code | Exit | Raised when |
|---|---|---|---|
SommelierError |
SOM000 | 5 | Base class; also the label the CLI gives unexpected exceptions |
UserInputError |
SOM002 | 2 | Bad flag combinations (--adapter with --model base), a missing raw input file, or report compare pointed at a directory without a resolved config |
ConfigError |
SOM201 | 2 | Config file missing, YAML that does not parse, or fields that fail schema validation |
SchemaValidationError |
SOM202 | 2 | A record with a missing or unsupported schema_version, or an artifact path that escapes the artifact root |
ArtifactNotFoundError |
SOM203 | 2 | A stage input that does not exist, typically a split the preceding stage never produced |
ExternalDependencyError |
SOM003 | 3 | An optional extra is not installed (torch and friends for training, cudf for --gpu, uvicorn for serving, wandb for tracking), or a release gate other than the secret scan fails |
RemoteExecutionError |
SOM004 | 4 | The classification for remote execution failures; in the current codebase it surfaces only through its subclass ResourceError |
ResourceError |
SOM401 | 4 | Training out of GPU memory, or a training dependency reporting a timeout |
EvaluationError |
SOM005 | 4 | A non-deterministic decoding config, a generation count that does not match the test split, or a comparison gate rejection |
InvariantViolation |
SOM005 | 5 | A prompt digest mismatch between formatting and evaluation, a broken prompt-target boundary, mixed decoding configs in one generations file, or a non-finite float in logs or metrics |
SecurityPolicyError |
SOM006 | 5 | Secret material detected in a config, manifest, report, or the artifact tree |
ResourceError deserves a note because it embodies a policy: Sommelier never retries with silently altered settings. An out-of-memory failure comes back with a hint naming train.per_device_batch_size, train.gradient_accumulation_steps, train.max_sequence_length, and remote.gpu with their current values. When a dependency reports a timeout, the hint quotes remote.train_timeout_seconds as planning context and directs you to inspect the provider's outer timeout. That legacy-named field does not install a training watchdog, so raising it alone cannot extend the provider deadline. Any config change lands in the resolved config digest, and the record stays honest.
One code, two exit codes¶
EvaluationError and InvariantViolation both carry SOM005 but exit with 4 and 5 respectively. This is a known wart in the v1 contract: the code alone cannot tell you whether evaluation hit a fixable condition or the pipeline broke one of its own guarantees. Branch on the exit code, not the error code, when scripting against Sommelier.
Failure evidence in manifests¶
The stage-manifest schema reserves a failed shape: status: "failed" plus error_code and error_message fields, with the message redacted at build time. If it contains any of hf_, sk-, ghp_, token, secret, or password (case-insensitive), the entire message is replaced with stage failed; details redacted; otherwise it is truncated to 500 characters, and the manifest passes the same no-secrets validator as every other artifact. In the current pipeline, a failing stage propagates its error to the CLI, which prints it and exits with the codes above, rather than writing a failed manifest; the absence of a stage's succeeded manifest is itself the failure evidence. Manifest structure is documented in Artifacts and schemas, and the wider redaction policy in Security.
For the full picture of why stages fail loudly instead of degrading, see The pipeline and Design decisions.