Error codes (E1–E15)¶
Every Punix-domain error is located (file:line:col) and coded ([E#]). The coding lets you grep operational logs for a category; the location lets you jump to the cause without context-switching.
The full surface:
| Code | Layer | Triggered by |
|---|---|---|
[E1] |
Frontend / check |
Type mismatch (wrong primitive, mismatched list element types, conditional branches don't agree) |
[E2] |
Frontend / check |
Unknown field, unknown member, undefined reference |
[E3] |
Frontend / check |
Required slot (option ℓ:τ) not provided; or conflicting definitions |
[E4] |
IR / Evaluator | CIA conflict — two same-priority contributions to one field |
[E5] |
Frontend / check |
Conditional-branch type mismatch (if cond then a else b where a and b have different types) |
[E6] |
Frontend / check |
Generic "shape doesn't match the type system's expectation" (catch-all for things outside E1–E5 in the typed AST) |
[E7] |
Build closure | Cyclic dependency, missing recipe on a dep'd module, host-side prerequisite violated before any realise runs |
[E10] |
Realise / Fetcher | Source-hash mismatch on a type=url fetch; or fetch failure |
[E11] |
Deploy / Secrets | A {from_env=…} or {from_file=…} reference is unset at deploy. Lists every missing name. |
[E12] |
Deploy / Manifest | Rollback with no previous generation, OR a pinned store_path is missing (GC contamination) |
[E13] |
Realise / Fixed-output | A type=fixed_output recipe's actual tree-hash doesn't match the declared one |
[E14] |
Realise / Multi-arch | A type=url recipe missing per-arch source for the target system |
[E15] |
Frontend / check |
Duplicate pname across two package modules in the composed tree. Multi-version packaging must use distinct pnames (e.g. python-3-13 vs python-3-14), never two modules with pname = "python". Two-site: cites both colliding modules. |
Deploy-layer runtime errors (uncoded, but located by message)¶
The deploy/realise half (below the eval/realise seam) raises domain errors that are not [E#]-coded — they surface at deploy time as error: <message> and exit 1, following the wrap-and-reraise convention rather than the frontend's located-and-coded one. They are caught by the CLI deploy handler so a deploy never crashes with a traceback:
| Error | Raised when |
|---|---|
StackComposeError |
A composed stack is malformed (missing required field, unknown package/service, invalid name/hostname/port/size, etc.). |
ConfigPathError |
A configFiles[].path (or a generator output path) is outside the allowed roots, hits the privilege-escalation denylist, or collides after normalization — see config safety (B3b / ADR-026). |
ArtifactError / ArtifactMissingError |
A cross-host deploy-time artifact can't be resolved, or the producer→consumer graph has a cycle — see cross-host artifacts (ADR-022). |
FleetError |
A fleet has an artifact-dependency cycle, or another whole-fleet precondition fails before any host runs. |
TlsError |
A referenced cert/key is missing or malformed at provision time. |
TransportError |
A process/IO/network operation through the Transport failed (this is exit 3, not 1). |
Anatomy of an error¶
stack.pcl:14:8— file, line, column. Click in a tooling-aware terminal; jump in $EDITOR.error:— the level.[E2]— the code.- A short message naming the gap.
- Optional
hint:lines with corrective suggestions.
Per-code details¶
[E1] Type mismatch¶
The frontend's structural type checker. Raised on:
- Wrong primitive (
version = 8whenStrexpected). - List-element type drift (
[1, "two"]). - Record-shape mismatch (passing
Rec<a: Int>whereRec<a: Str>expected).
[E2] Unknown field / undefined reference¶
Single-syntax-pass — every reference is resolved against the typed namespace; misspellings and missing modules surface here.
[E3] Required slot not provided / conflicting definitions¶
stack.pcl:1:1: error: [E3] module 'Database' missing required field 'dsn'
declared at stack.pcl:3:3 as 'option dsn: Str'
Or:
Two contributions can coexist (use scenarios + Setting<τ>); two definitions cannot.
[E4] CIA conflict¶
stack.pcl: error: [E4] conflicting contributions to 'Db.host':
base.pcl:8:3: Db.host = "localhost" (priority 50)
prod.pcl:14:3: Db.host = "db.prod.example" (priority 50)
same priority; arbitration cannot pick a winner
CIA (conflict-aware inheritance with arbitration) needs exactly one winner per field. Either change a priority (Setting<Str>(value, priority=100)) or remove a contribution.
[E5] Conditional-branch type mismatch¶
Conditionals are typed structurally — both branches must agree.
[E6] Catch-all type-shape error¶
Used for situations where the typed AST doesn't admit the source — rare in practice; usually means the parser produced something the checker doesn't know how to walk. Treat as a bug to file.
[E7] Build-closure error (pre-realise)¶
Or:
The build-closure pass walks the dep graph once before any realise runs. Catches cycles and dangling refs before the first sandbox spawn.
[E10] Source-hash mismatch / fetch failure¶
Or:
For type=url sources. The declared hash field is mandatory; a mismatch means either the upstream changed (security event) or the hash was wrong from the start.
[E11] Missing secret¶
Names are sorted, deduplicated, kind-qualified. The design contract requires the message to list every missing reference, not just the first. See Secrets at deploy.
[E12] Rollback contract violation¶
Two flavours:
error: [E12] stack 'MyStack' gen-001: pinned store path missing:
/store/abc...-curl-8.5.0
/store/def...-openssl-3.0.0
See Rollback for recovery.
[E13] Fixed-output hash mismatch¶
A type=fixed_output recipe produced a tree that doesn't hash to the declared value. Inline output is truncated; the per-package log carries the full message (look for (log: …)).
The two recoveries:
- Wrong declared hash — paste the
gotvalue into the PCL, re-run. - Upstream tampered — investigate.
[E14] Multi-arch source missing¶
error: [E14] go-bootstrap: no per-arch source for target system
'aarch64-linux' (declared: x86_64-linux, aarch64-darwin). This binary
source is per-OS/arch; build on a declared arch (Linux is the deploy
target — Mac is dev-only).
For type=url_per_arch sources. The perArch array names the systems the source is available for; the deploy target must match one. The message names the missing target AND the available declarations so the operator sees the gap and the candidates at a glance.
[E15] Duplicate pname across package modules¶
packages/dogfood/auto/autoconf.pcl:2: error: [E15] duplicate pname 'autoconf':
declared by module 'autoconf' and 1 other
packages/nix/tools/autoconf.pcl:8: note: also declared by module 'Autoconf'
Within one composed tree every package module's pname must be unique — user-facing commands (punix install, punix search, punix info) identify packages by pname, so two modules with the same pname are ambiguous by construction.
Resolutions:
- Multi-version packaging — use distinct pnames:
pname = "python-3-13"andpname = "python-3-14", never twopname = "python". - Multiple package collections — keep them in separate trees. Don't compose
packages/nix/andpackages/slapos/together; invoke each via its own--file packages-slapos/. Seepunix overview > Default FILE discovery. - Auto-imported vs hand-port duplicates — delete the auto copy when promoting to hand-port (the methodology already in
notes/dogfooding/03-per-package-fix-methodology.md).
The error cites every colliding site in one pass so you don't have to fix-and-retry.
Where in the code¶
src/punix/frontend/errors.py::PunixError— the located error type.src/punix/frontend/types.py— emits E1–E6.src/punix/frontend/closure.py— emits E7.src/punix/frontend/unique_pname.py— emits E15.src/punix/realise/realise.py— emits E10, E14.src/punix/realise/output_hash.py::OutputHashMismatchError— E13.src/punix/deploy/secrets.py::SecretMissingError— E11.src/punix/deploy/manifest.py::GenerationPathMissingError— E12.src/punix/deploy/validate.py::ConfigPathError— uncoded path-confinement domain error.src/punix/deploy/artifacts.py::{ArtifactError,ArtifactMissingError},deploy/fleet.py::FleetError,deploy/stack.py::StackComposeError,deploy/tls.py::TlsError— uncoded deploy-layer domain errors.
Conformance¶
The conformance suite ships one minimal program per code producing exactly that error, located. Find it in tests/c_e2e/test_conformance_stage1.py (E1–E6), test_conformance_stage3.py (E7, E10), and the Stage 6 suite for E11 / E13 / E14.
Related¶
- Exit codes — how E# maps to process exit codes.
- Conformance — the property→test map.