Skip to content

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: error: [E2] unknown field 'pacakge' on module 'Stack'
  hint: did you mean 'package'?
  • 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

stack.pcl:5:3: error: [E1] type mismatch in 'version':
expected Str, got Int

The frontend's structural type checker. Raised on:

  • Wrong primitive (version = 8 when Str expected).
  • List-element type drift ([1, "two"]).
  • Record-shape mismatch (passing Rec<a: Int> where Rec<a: Str> expected).

[E2] Unknown field / undefined reference

stack.pcl:14:8: error: [E2] unknown field 'pacakge' on module 'Stack'
  hint: did you mean 'package'?
stack.pcl:20:12: error: [E2] reference 'Db.unknown_field' is not defined

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:

stack.pcl:1:1: error: [E3] module 'Caddy' redefined
  first defined at base.pcl:5:1

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

stack.pcl:8:3: error: [E5] 'if' branches have different types:
  then-branch: Str
  else-branch: Int

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)

error: [E7] cyclic dependency in build closure:
  Caddy → Openssl → Caddy

Or:

error: [E7] services[0].package = 'NonexistentMod' is not a package module

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

error: [E10] curl: source hash mismatch
  declared: sha256:abc...
  observed: sha256:xyz...

Or:

error: [E10] curl: fetch failed: 404 Not Found

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

error: [E11] secret(s) not set: from_env:DB_PWD, from_file:/run/secrets/jwt

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': no previous generation (current = 1)
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

error: [E13] output hash mismatch for /sandbox/.../tmp:
  expected: sha256:abc...
  got:      sha256:xyz...

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 got value 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" and pname = "python-3-14", never two pname = "python".
  • Multiple package collections — keep them in separate trees. Don't compose packages/nix/ and packages/slapos/ together; invoke each via its own --file packages-slapos/. See punix 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.