This page is the canonical reference for d2b's shared exit-code anchors and stable typed-error kinds.
Operator-visible typed errors use stable leaf Kind discriminants and reserved
exit codes in 10..=99. Shared command-local exits such as generic failures,
usage errors, and legacy-bash lock conflicts are documented separately below so
docs/reference/cli-contract.md can link to a real target for every anchor.
These anchors document common process exits that are not emitted as
d2b_core::error::Error envelopes.
The table below is generated by cargo xtask gen-error-codes from
d2b_core::error::Error::all_kinds(). Each row is a stable leaf Kind
discriminant with its reserved exit code, owning command, redacted message
shape, remediation hint, and docs anchor.
These daemon wire errors carry only a closed kind. They never include helper diagnostics, shell names, public session handles, terminal bytes, supervisor metadata, paths, environment, or cwd.
The CLI host verbs (d2b host prepare, host destroy,
host doctor, host install) use a richer 7-field operator-UX
envelope distinct from the typed daemon-API envelope above. The
shape is { kind, code, exit_code, what_was_checked, observed_state, remediation, docs_anchor }; the goldens under
tests/golden/cli-output/host-*-*.{json,txt} are the authoritative
spec.
The relationship between the two envelopes:
- Daemon-API typed envelope (
{kind, code, message, remediation, docsAnchor, owningCommand}perdaemon-api.md) is whatd2bd, the broker, and the wire layer emit. CLI surfaces that proxy daemon-API responses (e.g.host check,auth status,audit) deserialize and re-render this shape. - CLI host-verb refusal envelope (the 7-field shape used by the
host verbs above) is what the CLI itself emits when it intercepts
a request before reaching the daemon, e.g. legacy refusals,
missing-flag usage errors, or typed
daemon-down/broker-error/not-yet-implementedcarve-outs. The richerwhat_was_checked/observed_statepair gives operators the context the typed envelope's baremessagefield omits.
Both envelopes anchor into the catalog below; the CLI's code
matches an #anchor here. The goldens are the contract.
Note: the tier-0-legacy-uses-nixos-module and
single-writer-conflict codes are anchored further down in the
"host-prepare audit decision codes" catalog because they
originate as broker-side audit decisions; the CLI re-uses the same
docs anchors when intercepting them before reaching the broker.
d2b vm exec applies its own reserved exit-code contract on top
of the daemon wire kind, so the CLI exit is stable regardless of the
daemon's fallback exitCode. A guest command's own WIFEXITED status
(0-255) passes through unchanged and CAN collide with these reserved
numbers; a --json run disambiguates via source + reason +
guestExitCode / transportExitCode (see
cli-contract.md). The reserved numbers
avoid the pre-existing CLI exits 2/3/33/78.
vm exec --json always emits exactly one terminal JSON document on
stdout for every outcome (including early transport / auth /
old-generation establishment failures); a usage error (bad --env,
--json combined with -i/-t, missing command) is source: "cli",
reason: "usage", exit 2.
The host-prepare path (cgroup delegation + pidfd handoff, network
reconcile, firewall coexistence, modules + devices + runner-shape)
introduces a family of kebab-case audit decision codes that the
privileged broker writes to its append-only audit log
(/var/lib/d2b/audit/broker-<utc-date>.jsonl) as the
error_kind field. They are not typed exit codes in the auto-
generated table above; they are the broker's structured
fail-closed reasons that map back to the audit record schema in
docs/reference/privileges.md § "Audit record
schema".
When surfaced through the daemon API, every audit decision code folds into the generic typed errors above:
- A
denied-refuseddecision →broker-validation-failed(exit31) with the kebab-case code in the opaque reason field and the same code in the broker audit log'serror_kind. - A
denied-unknowndecision (unknown variant / unknown field) →broker-validation-failed(exit31) witherror_kind= unknown-subject(or the analogous kebab code) and the trusted bundle hash for cross-reference. - An
erroreddecision →internal-io(exit50) with the same kebab-caseerror_kindin the audit record.
This split means CLI consumers continue to script against the small typed-error catalog above, while operators reading the audit log get the fine-grained kebab-case reason that the broker recorded.
| Audit code | Owner ADR | Origin op(s) | Maps to typed error |
|---|---|---|---|
cgroup-controllers-missing |
0011 | DelegateCgroupV2 |
broker-validation-failed |
cgroup-v2-unified-not-present |
0011 | DelegateCgroupV2 |
broker-validation-failed |
cgroup-delegation-refused |
0011 | DelegateCgroupV2 |
broker-validation-failed |
cgroup-kill-on-ancestor-refused |
0011 | CgroupKill (broker-mediated only — no daemon-direct cgroup.kill writes; see cgroup-delegation.md "Broker ops on the cgroup tree") |
broker-validation-failed |
ifname-too-long |
0012 | CreateTapFd, CreatePersistentTap |
wire-ifname-invalid |
ifname-collision |
0012 | bundle render + CreateTapFd |
broker-validation-failed |
bridge-port-flag-drift |
0012 | SetBridgePortFlags |
broker-validation-failed |
ipv6-sysctl-drift |
0012 | ApplySysctl |
broker-validation-failed |
foreign-nft-rule-preserved |
0013 | ApplyNftables (informational; not a fail) |
n/a |
nft-coexistence-firewalld-refuse |
0013 | ApplyNftables |
broker-validation-failed |
nft-coexistence-ufw-refuse |
0013 | ApplyNftables |
broker-validation-failed |
nft-coexistence-docker-require-unmanaged |
0013 | ApplyNftables |
broker-validation-failed |
nft-coexistence-libvirt-require-unmanaged |
0013 | ApplyNftables |
broker-validation-failed |
nft-coexistence-iptables-nft-priority-readback |
0013 | ApplyNftables |
broker-validation-failed |
nft-coexistence-unknown-manager-refuse |
0013 | ApplyNftables |
broker-validation-failed |
nft-coexistence-no-manager-coexist |
0013 | ApplyNftables (informational) |
n/a |
nm-managed-foreign-conflict |
0012 | ApplyNmUnmanaged |
broker-validation-failed |
nm-reload-required |
0012 | ApplyNmUnmanaged |
broker-validation-failed |
route-preflight-no-default-route |
0012 | ApplyRoute |
broker-validation-failed |
route-preflight-foreign-default-route |
0012 | ApplyRoute |
broker-validation-failed |
dnsmasq-not-bound |
0012 | ApplyRoute (preflight) |
broker-validation-failed |
host-lan-cidr-ambiguous |
0012 | ApplyRoute (preflight) |
broker-validation-failed |
modules-disabled-sysctl-locked |
0014 | ModprobeIfAllowed |
broker-validation-failed |
host-modules-locked |
0014 | ModprobeIfAllowed |
broker-validation-failed |
modprobe-denied-not-in-matrix |
0014 | ModprobeIfAllowed |
broker-validation-failed |
minijail-too-old |
0014 | runner-shape preflight | broker-validation-failed |
path-safety-violation |
0012, 0013 | UpdateHostsFile, ApplyNmUnmanaged, PrepareStateDir, PrepareRuntimeDir |
broker-validation-failed |
single-writer-conflict |
0011 | any host-prepare op when legacy systemd + daemon coexist | broker-validation-failed |
ch-net-handoff-not-supported |
0014 | runner-shape preflight | broker-validation-failed |
runner-shape-drift |
0014 | runner-shape preflight | broker-validation-failed |
tier-0-legacy-uses-nixos-module |
0014 | host prepare --apply on the legacy NixOS-module path |
exit 78 (host-prepare carve-out) |
host check --apply summarizes any denied-refused / errored
decisions per VM and returns exit 2 (#host-check-failure) when
any required prerequisite fails. Advisory-only denied-refused
findings return exit 1 (#host-check-warning).
The reference shape below is normative for the migration-guide cross-link remediation hint. The Rust-side rendering changes live in
packages/d2b-core/src/error/remediation_rendering.rsand are wired into the typed-envelopeDisplayimpl for the four verbs listed below.
Most typed-error envelopes render Remediation: as a single-line
hint inline with the envelope JSON / human pretty-printing. The
historical inline rendering is preserved unchanged for every
envelope EXCEPT the four cases below; operator grep patterns
targeting other remediation strings are unaffected.
Multi-line Remediation: block format applies to ONLY
these two CLI verbs when they emit #daemon-down or
#not-yet-implemented envelopes pointing operators at the
migration guide:
d2b audit(andd2b audit --strict)d2b keys(read-only subcommands)
The multi-line block format renders differently for the two verb categories described above:
Category 1 — Truly deferred verbs (audit --strict) emit
#not-yet-implemented (exit 78) unconditionally. The remediation
block renders as:
Remediation:
This subcommand is queued for a later release.
See the operator migration runbook:
docs/how-to/migrate-d2b-v0-to-v1.md
Specifically the "<verb-specific anchor>" section.
Category 2 — Daemon-down rendering pointers (audit without
--strict, keys list, keys show) emit #daemon-down (exit 1)
only when the broker is stopped; otherwise the successful
invocation path runs normally. When #daemon-down does fire, the
remediation block renders as:
Remediation:
d2bd is not reachable. Start the daemon and re-run:
sudo systemctl start d2b-priv-broker.socket
sudo systemctl start d2bd.service
For the full operator runbook context, see:
docs/how-to/migrate-d2b-v0-to-v1.md
Specifically the "<verb-specific anchor>" section.
Indentation is exactly 2 spaces per nesting level; the URL on its
own line ensures the full path is copy-paste-safe on terminals
narrower than 80 columns (the old inline rendering wrapped the
URL across lines on narrow terminals, breaking
copy-paste). Operator-facing grep patterns previously matching
Remediation: <inline-hint> on the two verbs above MUST switch
to matching Remediation: as a line prefix followed by the
multi-line block; grep patterns targeting other envelopes (e.g.,
#bridge-port-flag-drift, #nft-coexistence-*) keep working
unchanged because their inline rendering is preserved.
The two affected verbs and their migration-guide anchors are:
d2b audit:docs/how-to/migrate-d2b-v0-to-v1.md#v11-deferred-verbs-audit(mixed disposition — non---strictis Category 2 daemon-down only;--strictis Category 1 truly deferred)d2b keys list/d2b keys show:docs/how-to/migrate-d2b-v0-to-v1.md#v11-deferred-verbs-keys(Category 2 — daemon-down only)
d2b console and d2b audio (subcommands status|mic|speaker|off)
are implemented and no longer emit #not-yet-implemented envelopes.
Their golden files (console-deferred.golden, audio-deferred.golden)
are retired. The Rust Display impl for the two remaining deferred verbs
is verified byte-for-byte against tests/golden/cli-output/audit-*-deferred.golden
and keys-deferred.golden.