Skip to content

Diagnostics

mur reports every problem it finds with a code. An E-* code is a failure that stops the command; a W-* code is a warning that lets it continue. The index lists every code with the section that explains it.

Index

Code Meaning Details
E-BLD-001 Manifest name: is not a valid artifact identifier, or carries a scope E-BLD-001
E-BLD-002 requires_files: entry is unsafe (absolute, .., symlink) or collides inside the archive, or a skill folder holds a symlink, two skill.md spellings or a murmur-build.json E-BLD-002
E-BLD-003 Packed entry set is not a launchable wasm payload E-BLD-003
E-BLD-004 Artifact manifest declares inference_auth:, the former name of upstream_auth: E-BLD-004
E-BLD-005 A driver's models: table is malformed — a max_input_tokens above its context_window included — or models: is declared on an artifact that is not a driver E-BLD-005
E-BLD-006 An artifact's provider_tool: block is malformed, or declared on a driver, hook or skill artifact E-BLD-006
E-CAP-001 A capabilities.network.allow entry could not be parsed E-CAP-001
E-CAP-002 A filesystem.scope is not relative to the workdir, or escapes it via .. E-CAP-002
E-CAP-003 Declared containment floor (advisory|scoped|sealed) is not achievable on this host, or a declared provider tool needs sealed and the effective floor is below it E-CAP-003
E-CAP-004 capabilities.shell.staged_runtime is declared below an effective sealed containment floor E-CAP-004
E-CAP-005 Creating a network namespace for the capsule's native subprocess tree — shell commands, spawned capsules and native tools — failed on this host, so capabilities.network.allow cannot be enforced for it E-CAP-005
E-CAP-006 Nothing declared makes an allowlisted interpreted entrypoint's package tree reachable inside a sealed composed root E-CAP-006
E-CAP-007 an export root resolves outside the work folder E-CAP-007
E-CAP-008 a persistent capsule declares exports.peer_files without a short enough max_ttl E-CAP-008
E-CAP-009 capabilities.state.store does not name a usable durable store E-CAP-009
E-CAP-010 An artifact entry's config: block cannot be delivered as MURMUR_ARTIFACT_CONFIG E-CAP-010
E-CAP-011 context.record_store, or mur run --context, does not name one conversation record directory E-CAP-011
E-CAP-012 A capabilities.filesystem.read_only entry is not a usable workdir subpath E-CAP-012
E-CAP-013 An artifact's tool name is the name of a tool the runtime provides itself E-CAP-013
E-CAP-014 A variable this project needs is set by nothing in the environment E-CAP-014
E-CAP-015 A capsule declares a capabilities.env.allow entry the capsule that spawns it does not hold E-CAP-015
E-CAP-016 A capabilities.env.allow entry names a variable the credential backstop strips from every WASM component E-CAP-016
E-CAP-017 A gateway: is declared on a native tool, whose requests never pass the credential gateway E-CAP-017
E-CAP-018 A gateway: names an endpoint but binds no api_key and does not declare keyless: true E-CAP-018
E-CAP-019 A process driver requires a variable capabilities.env.allow does not declare E-CAP-019
E-CAP-020 Two declared artifacts share a tool name, the part of their names after the / E-CAP-020
E-CNV-001 No such record store or context id under ~/.murmur/conversations/ E-CNV-001
E-CNV-002 A context id is present under more than one record store E-CNV-002
E-CNV-003 mur conversation truncate --keep is not a usable number of messages to keep E-CNV-003
E-CFG-001 No inference provider configured and wizard cannot run in non-interactive mode mur new
E-CFG-002 mur config set given an unsupported dotted key mur config
E-CFG-003 registry: in a config file cannot be used: a removed default: key, a source's name:, type: or url:, a repo that is not <owner>/<repo>, a reserved scope claimed, or two sources for one scope E-CFG-003
E-DEPLOY-001 No --host given, or an --env value is not KEY=VALUE mur deploy run
E-DEPLOY-003 SSH connection or remote command failed mur deploy run
E-DEPLOY-004 Capsule did not emit usable startup JSON within 120s mur deploy run
E-DEPLOY-006 The pinned mur release could not be fetched from GitHub mur deploy run
E-EVAL-001 Eval file parse error (malformed JSON, unknown record_type, missing required field); message includes :line: number eval.jsonl schema
E-EVAL-002 No eval file found for the session named on the command line, or no eval-run record for the run mur eval diff --run names mur eval
E-EVAL-003 A dataset case's start or check.files is missing, is not a directory, or holds a symbolic link or special file; its check.command is empty; or its check.timeout_secs is 0. Names the case and the path or field mur eval run
E-EVAL-004 A case's check binary is not in an arm's capabilities.shell.allow (names the case, the arm and the binary), or the baseline's and the variant's inference.model differ (names both) mur eval run
E-IDX-001 A file in the directory given to mur index cannot be indexed, or the directory holds no .mur.zip E-IDX-001
E-IDX-002 An artifact index is not a schema 2 index this mur reads: schema 1, another schema, not JSON, or invalid; or a source's latest release has no index E-IDX-002
E-IDX-003 mur index was not given a usable source: --repo or --release is unusable, or --merge names an index of another scope E-IDX-003
E-IO-001 File or directory not found —
E-IO-002 Permission denied on a host path (the host's own permissions, not a capsule capability) —
E-IO-003 General I/O error (read/write failure), including a write to standard output that failed for a reason other than its reader going away Piped output
E-IO-004 HOME is unset, relative, or names a directory in which ~/.murmur cannot be created, so there is nowhere to keep a session's runtime and records directories E-IO-004
E-MAN-001 Missing required manifest field —
E-MAN-002 YAML syntax error in manifest —
E-MAN-003 Field type mismatch in manifest, or a structurally valid value the runtime rejects (artifact entry, inference config, capability config), a removed inference.endpoint or inference.api_key, a gateway.endpoint with userinfo, ${ or another rejected shape, a gateway: that writes both api_key and keyless: true, a malformed network.authentication, a credential scope that names a retired door method, a network.authentication declared beside a non-empty capabilities.spawn.allow, a lifecycle.queue_depth: 0 under task_acceptance: queue, a lifecycle.keep_working block with a missing or blank message or a missing or zero limit, a lifecycle.done_check block with a missing or blank command, a command whose program capabilities.shell.allow does not list, a missing limit or a zero timeout_secs, a top-level mur_version, which is pinned as deploy.mur_version, a gateway.api_key: ${NAME} that neither credentials.NAME in ~/.murmur/config.yaml nor the environment variable NAME holds, an artifact name that is not <scope>/<name> where a full name is required, a scope on a source: entry, two capabilities.spawn.allow entries with one bare part, or a top-level registry: key E-MAN-003
E-NEW-001 The generator agent produced no out/murmur.yaml mur new
E-NEW-002 The name given to mur new --roster, or a capsule name derived from it, is not an artifact name mur new --roster
E-NEW-003 The directory mur new --roster would write already exists mur new --roster
E-NEW-004 inference.provider in ~/.murmur/config.yaml names a provider mur new --roster has no driver for mur new --roster
E-NEW-005 mur new was given --index local, the global store, which is not a published artifact index mur new
E-NEW-006 mur new could not read the artifact index it takes default-artifact versions from E-NEW-006
E-NEW-007 The artifact index mur new read lists no version of a default artifact that fits this mur E-NEW-007
E-REG-001 The scope's index lists no such name, no such version, or no payload of it for the host platform, or no version that fits this mur; or a local/ artifact is not in the store, and no source serves the local scope E-REG-001
E-REG-002 Artifact bytes do not match the sha256 recorded for them or listed in the source's index, or an artifact's packed name: or version: is not the one it is installed as E-REG-002
E-REG-003 An artifact of that name and version is already published mur publish
E-REG-004 Reserved version string (latest, stable, edge), given to mur publish or mur install mur install
E-REG-005 A registry-resolved artifact's version or hash, or a roster member's, disagrees with the murmur.lock entry Lockfile
E-REG-006 A source did not answer an artifact lookup — rate limited, refused or unreachable — so whether it publishes the artifact is not known E-REG-006
E-REG-007 An artifact name on the command line is not <scope>/<name>, or a github: owner claims a reserved scope E-REG-007
E-REG-008 No source serves the artifact's scope E-REG-008
E-REG-009 A source's index states a scope the source does not own E-REG-009
E-REG-010 A scope's GitHub owner id is not the one recorded for it in murmur.lock, in the stored payload, or compiled in for murmur E-REG-010
E-REG-011 mur install or mur publish was handed a payload whose packed murmur.yaml ships a malformed models: table E-REG-011
E-ROS-001 roster.yaml is missing, unreadable, or does not have the roster shape, or names a member's capsule by a bare name E-ROS-001
E-ROS-002 A roster marks no member entry: true, or more than one E-ROS-002
E-ROS-003 Two roster members share one name E-ROS-003
E-ROS-004 A reachability rule names a member the roster does not have E-ROS-004
E-ROS-005 A reachability rule calls a member that does not declare exports.peer_tasks.accept: true E-ROS-005
E-ROS-006 A roster with peer traffic has a member with no network.authentication E-ROS-006
E-ROS-007 A roster member is not installed at its version, or its packed murmur.yaml cannot be read E-ROS-007
E-ROS-008 A reachability rule lists the entry member in to E-ROS-008
E-RUN-001 Capsule crashed, compile failure, missing component export, execution deadline exceeded (capabilities.limits.deadline_seconds), or resource limit exceeded (capabilities.limits.memory_bytes/table_elements) Execution limits
E-RUN-002 Missing WASI import (linker error) —
E-RUN-003 A lock_version other than 3, a lock entry not named <scope>/<name>, missing lock entry, a lock entry with no hash for this host's platform, or a malformed origin / session pair Lockfile
E-RUN-004 Capsule WASM not found at expected path —
E-RUN-005 Inference driver not configured in manifest Inference configuration
E-RUN-006 Inference driver artifact not installed, or a transport: process harness binary was not found E-RUN-006
E-RUN-007 Agent loop failed at runtime —
E-RUN-008 Required artifact not installed locally mur run
E-RUN-009 inference.system_prompt_file (or the compaction system-prompt file) could not be read inference.system_prompt
E-RUN-010 network.internal_port is already bound —
E-RUN-011 A native subprocess was killed for exceeding a capabilities.resources limit Which limit a subprocess hit
E-RUN-012 The capsule can spawn native subprocesses but no cgroup v2 scope could be delegated to bound them (Linux only) Platform behavior
E-RUN-013 The work folder, $HOME and $TMPDIR together grew past capabilities.resources.workdir_max_bytes Size ceiling
E-RUN-014 A sealed session cleared the host probe at launch but its composed root could not be built for a subprocess Containment class
E-RUN-015 mur run --resume and --context were both given E-RUN-015
E-RUN-016 The session --resume named records no task_start carrying a context id E-RUN-016
E-RUN-017 The context --resume resolved to kept no conversation record E-RUN-017
E-RUN-018 --resume-mode compact with no hook bound to on-compaction E-RUN-018
E-RUN-019 A session that can delegate could not register with mur-roost E-RUN-019
E-RUN-020 MURMUR_SPAWNER is set to something that is not a spawner handle, or MURMUR_SPAWNER_LIFELINE to something that is not this session's spawner lifeline E-RUN-020
E-RUN-021 A staged native tool's binary is built for another operating system or CPU architecture E-RUN-021
E-RUN-022 A session address or formation id names no capsule running on this machine E-RUN-022
E-RUN-023 The capsule a session address named is running and did not answer E-RUN-023
E-RUN-024 The session or formation named could not be ended and is still running, or mur trace rm named a session that is still running E-RUN-024
E-RUN-025 An artifact whose entry declares gateway: has no usable upstream_auth: block E-RUN-025
E-RUN-026 spend.machine_tokens_per_day is set and the spend ledger under ~/.murmur/spend cannot be used E-RUN-026
E-RUN-027 The provider kept rejecting the inference credential after it was re-read E-RUN-027
E-RUN-028 The running-capsule records under ~/.murmur/running/ could not be read E-RUN-028
E-RUN-029 A transport: process driver exports no version of the process interface, or a driver imports a murmur:stream interface that does not exist at the served version E-RUN-029
E-RUN-030 The transport: http inference driver exports the process driver interface E-RUN-030
E-RUN-032 A process driver could not be loaded with no grants, or described itself unusably E-RUN-032
E-RUN-033 A transport: process turn failed — the harness reported it, or the runtime ended it at the turn limit E-RUN-033
E-RUN-034 A call into the process driver refused, crashed or ran out of time E-RUN-034
E-RUN-035 A transport: process harness went silent and was killed E-RUN-035
E-RUN-036 The harness could not find the session it was asked to continue E-RUN-036
E-RUN-037 --resume-mode compact under inference.transport: process E-RUN-037
E-RUN-038 A spend ceiling is set against a process driver that reports no usage E-RUN-038
E-RUN-039 A forget was asked of a capsule that keeps no harness session E-RUN-039
E-RUN-040 The session ran and its task did not complete: it failed, spent inference.max_turns, hit a spend ceiling, or was canceled E-RUN-040
E-RUN-041 A capsule declaring control: could not write its control token beside its running record, and did not launch E-RUN-041
E-RUN-042 mur control named a session with no control surface, or the control surface refused the request E-RUN-042
E-RUN-043 A murmur.lock pin written by manage.pull() is declared as runtime: hook, runtime: driver, with gateway:, or bound as inference.system_prompt_artifact E-RUN-043
E-RUN-044 MURMUR_FORMATION_ID is set to something that is not a formation id E-RUN-044
E-RUN-045 A formation member did not come up, so mur run --roster launched nothing and stopped every member it had started E-RUN-045
E-RUN-046 mur run --roster was run inside a formation member, a member's formation channel cannot be read, MURMUR_FORMATION_PEERS is set in mur run's environment, or MURMUR_FORMATION_LIFELINE does not name this member's lifeline E-RUN-046
E-RUN-047 An installed capsule's bytes no longer hash to the sha256 roster admission bound its member to E-RUN-047
E-RUN-048 mur token named a session whose door is public, which minted no token E-RUN-048
E-RUN-049 mur token --credential named a credential the session did not mint E-RUN-049
E-RUN-050 The runtime met a symbolic link, or the wrong kind of file, inside the workdir — while staging the session, or during the task mur run ran — or mur trace rm met one on the way to a directory it removes E-RUN-050
E-RUN-051 A delegated child's --workdir is not the directory its parent created; nothing was written E-RUN-051
E-RUN-052 A driver choice has no output cap — its driver lists no limits for its model and the manifest sets no inference.max_tokens — or its driver ships a malformed models: table, a max_input_tokens above its context_window included E-RUN-052
E-RUN-053 The session reached its deadline, own or inherited, and was ended; a formation's --deadline fell before its entry member started; or a launch was handed a deadline it cannot enforce E-RUN-053
E-RUN-054 inference.compaction.max_tokens is above the output ceiling the primary driver's models: table declares for the compaction model E-RUN-054
E-RUN-055 A driver choice's output cap, or inference.compaction.max_tokens, is at or above its context window, leaving its calls no input budget E-RUN-055
E-RUN-056 The running session a session address named is a script capsule, which binds no door to connect to E-RUN-056
E-RUN-057 A capsule declares a computer tool and a transport: http driver choice runs a model its driver marks vision: false E-RUN-057
E-RUN-058 A stored tool artifact ships a malformed provider_tool: block, or one on an artifact that is not a tool E-RUN-058
E-TOP-001 Tempo endpoint unreachable, or invalid --window format mur topology
E-TOP-002 Tempo HTTP query failed (search or trace fetch) mur topology
E-TOP-003 Tempo response JSON parse failure mur topology
E-TRC-001 Trace file parse error (malformed JSON, missing required session_start, empty file); unknown event types are silently skipped. mur trace show reads a trace with no session_end, and skips a torn final line; mur trace diff and mur trace report refuse both, and a formation member's missing session_end names its formation and mur trace show <formation-id>. Also a mur trace show --body selector that names no recorded hash, or a hash whose body was never stored, a --turn that names several turns, --body or --turn with a formation id, and mur trace steps with a formation id, which names the mur trace show <formation-id> that lists its members and calls trace.jsonl schema, mur trace show --body, Listing a formation, mur trace steps
E-TRC-002 No session found in the workdir, or a session selector matched none or several. Also a frm_ argument to mur trace show that is not a formation id, or a formation no session under any searched root belongs to, and a literal path given to mur trace rm mur trace, Listing a formation, mur trace rm
E-WIT-LEVEL-001 A driver a launch stages, an artifact version mur install names, or a version a runtime pull resolves, does not fit the WIT level this mur serves E-WIT-LEVEL-001
W-BLD-001 A declaration names an archive entry the packer already fills W-BLD-001
W-BLD-002 capsule.wasm shadows another root *.wasm W-BLD-002
W-BLD-003 A compiled artifact packages build inputs W-BLD-003
W-DEPLOY-001 A deploy's target compiled none, or not all, of the uploaded artifacts; those compile on the capsule's first launch W-DEPLOY-001
W-DEPLOY-002 A deploy's target runs under a different MURMUR_MAX_ARTIFACT_DECOMPRESSED_BYTES from the deploying machine W-DEPLOY-002
W-REG-001 An installed native artifact has no recorded platform W-REG-001
W-REG-002 A capsule in the spawn closure could not be inspected W-REG-002
W-REG-003 An installed artifact does not fit the WIT level this mur serves W-REG-003
W-ROS-001 A roster lets more members call a member than its lifecycle holds at once W-ROS-001
W-RUN-001 A turn stopped at the inference.max_tokens output cap W-RUN-001
W-RUN-002 A transport: process harness reports a version its process driver was not tested against W-RUN-002
W-RUN-003 An inference.alternates driver choice's credential was found nowhere at launch, so the choice is unavailable W-RUN-003
W-RUN-004 A tool's input_schema is malformed where the required-field check reads it, so its calls run unchecked W-RUN-004
W-RUN-007 A session carries a formation id but no lifeline, so it does not wind down when its formation ends W-RUN-007
W-RUN-008 A formation member the roster lets call another reaches no member's door with its capabilities.network.allow W-RUN-008
W-RUN-009 A driver choice's driver lists no limits for its model, so it runs on inference.max_tokens with no context window W-RUN-009
W-SEC-001 No kernel-level subprocess sandbox on this platform W-SEC-001
W-SEC-002 Linux host without Landlock — filesystem scope and exec unenforced W-SEC-002
W-SEC-003 network.allow doesn't constrain bash's own outbound connections W-SEC-003
W-SEC-004 Literal secret value found in a manifest field W-SEC-004
W-SEC-005 Linux kernel enforcement is in force — what it covers, and the one key that re-widens it W-SEC-005
W-SEC-006 A hook's capabilities: block declares a sub-key that is inert on hooks — any but network, filesystem, state, task_io, conversation and limits.deadline_seconds W-SEC-006
W-SEC-007 A tool/driver narrowed to a host the capsule-wide ceiling does not allow — the entry was dropped W-SEC-007
W-SEC-008 A tool/driver capabilities: block declares something per-artifact narrowing does not apply W-SEC-008
W-SEC-009 capabilities.shell.interpreter_runtime couples the capsule to a specific host interpreter-version layout W-SEC-009
W-SEC-010 No cgroup on this platform — the subprocess tree has no aggregate memory/pids/cpu bound W-SEC-010
W-SEC-011 An executable workdir makes capabilities.shell.allow advisory W-SEC-011
W-SEC-012 A compiler driver's helper binaries have no Execute grant under sealed W-SEC-012
W-SEC-013 Unprivileged user namespaces are unrestricted host-wide, not granted to mur by the shipped AppArmor profile W-SEC-013
W-SEC-014 A capsule-wide capabilities.state block grants nothing — a durable store is granted per artifact W-SEC-014
W-SEC-016 A capsule-wide capabilities.conversation block grants nothing — the grant is per hook W-SEC-016
W-SEC-017 capabilities.filesystem.read_only is advisory for an allowlisted interpreter W-SEC-017
W-SEC-018 An installed tool's input_schema leaves a path-shaped property undeclared, so calls naming it are judged by key name W-SEC-018
W-SEC-019 A key in murmur.yaml this build does not recognize was parsed and ignored W-SEC-019
W-SEC-021 A cgroup scope was created and the declared cgroup_io_bytes_per_sec ceiling did not apply to it W-SEC-021
W-SEC-023 A session opened its door and its running-capsule record could not be written W-SEC-023
W-SEC-024 capabilities.env.allow names a credential-shaped variable — the grant hands the capsule a secret murmur does not broker W-SEC-024
W-SEC-025 A capabilities.network.allow entry names an artifact's gateway.endpoint host — the gateway does not use the entry, and it grants direct reach to that host without the key W-SEC-025
W-SEC-027 An artifact's gateway.api_key is read only at launch — from the environment or a literal in murmur.yaml — so a rotated key does not reach the running capsule W-SEC-027
W-SEC-028 A file under ~/.murmur that holds a secret, or the config file a gateway.api_key was read from, is readable by other accounts W-SEC-028
W-SEC-029 A compiler driver could not be run to ask where its helper binaries live, so W-SEC-012 was not evaluated for it W-SEC-029
W-SEC-030 An artifact reaches its upstream through an unmetered credential gateway, which the spend ceilings do not cover W-SEC-030
W-SEC-031 A transport: process harness session reports it is authenticating as something other than a subscription W-SEC-031
W-SEC-032 The A2A door is exposed off loopback and network.authentication is not declared W-SEC-032
W-SEC-033 The session's runtime or records directory lies inside the workdir, so the capsule's commands can change the files the runtime owns or the records it keeps W-SEC-033

Manifest errors

E-MAN-003 — a manifest value the runtime rejects

A field in murmur.yaml has the wrong type, or a value of the right type the runtime refuses. The message names the field and the value; the cases are listed in the index, and a gateway.api_key: ${NAME} that nothing holds is explained under Where ${NAME} is read from.

A credential scope that is a retired name of a door method — an A2A 0.3 method name, or an older name of a murmur method — is refused, and the hint names the scope to write in its place:

error[E-MAN-003]: murmur.yaml: invalid network config for 'network.authentication.credentials.watcher.scopes': 'tasks/get' is a retired name of a door method, and the door answers only the names its card lists
  hint: write 'GetTask' in place of 'tasks/get'
error[E-MAN-003]: murmur.yaml: invalid network config for 'network.authentication.credentials.watcher.scopes': 'stream/watch' is a retired name of a door method, and the door answers only the names its card lists
  hint: write 'murmur.WatchSession' in place of 'stream/watch'

agent/getAuthenticatedExtendedCard has no replacement scope, because every valid token may call GetExtendedAgentCard:

  hint: remove 'agent/getAuthenticatedExtendedCard': every authenticated caller may call GetExtendedAgentCard, so a credential lists no scope for it

The scopes are listed under network.authentication.

An artifacts: entry, a capabilities.spawn.allow entry and a capabilities.install entry name an artifact by its full name, <scope>/<name>. A bare name is refused, and the hint names its scoped forms — see Artifact names:

error[E-MAN-003]: murmur.yaml: invalid artifact name in 'artifacts[0].name': 'tool-edit' is not a full artifact name: an artifact is named <scope>/<name>
  hint: name it with its scope: 'murmur/tool-edit' for a Murmur default artifact, '<owner>/tool-edit' for one published from github.com/<owner>, or 'local/tool-edit' for one installed from a .mur.zip file
Manifest Refused because
A source: entry named ana/style A source: entry is named by its bare name; the hint is drop the scope: name it 'style'
capabilities.spawn.allow: [local/worker, ana/worker] delegate-task offers each capsule to the model by its bare part, so two entries may not share one
capabilities.install.tool: ["tool-*"] An install entry names its scope: ana/tool-*, ana/*, an exact full name, or *
inference.driver.artifact: driver-anthropic beside a declared murmur/driver-anthropic inference.driver.artifact names the entry as written; the message names the declared entry to refer to

A capabilities.shell.allow entry is a bare binary name, the one the session looks up on PATH. An entry that is anything else is refused when the manifest is read, before anything is staged or installed — see Shell allow:

error[E-MAN-003]: murmur.yaml: invalid capability config for 'capabilities.shell.allow[1]': '/opt/uv/bin/uv' is a path; an entry is a bare binary name
  hint: name the binary bare (uv, not /opt/uv/bin/uv) and put its directory on PATH for mur run: PATH=/opt/uv/bin:$PATH mur run …
Entry Reason in the message
/opt/uv/bin/uv, bin/uv is a path
., .. names a directory
"" is empty
"a b" contains whitespace
An entry holding a NUL character contains a NUL byte

A top-level registry: key is refused by every command that reads a murmur.yaml, before any other field is checked. Artifacts resolve from the stores and the sources in ~/.murmur/config.yaml; see Resolution order:

error[E-MAN-003]: murmur.yaml: 'registry' is not a manifest key
  hint: remove it; artifacts resolve from the project store (.murmur/artifacts/), the global store (~/.murmur/artifacts/), and then the sources under registry.sources in ~/.murmur/config.yaml

A lifecycle.queue_depth of 0 under task_acceptance: queue leaves the queue no room for a waiting task, and is refused before anything launches:

error[E-MAN-003]: murmur.yaml: invalid lifecycle config for 'lifecycle.queue_depth': 0 leaves task_acceptance: queue no room for a waiting task
  hint: set lifecycle.queue_depth to 1 or more, or use lifecycle.task_acceptance: single to take one task at a time

mur run --lifecycle-task-acceptance queue over a manifest that declares queue_depth: 0 under another acceptance is refused the same way:

error[E-MAN-003]: the launch sets lifecycle.task_acceptance to queue, and murmur.yaml declares lifecycle.queue_depth: 0, which leaves the queue no room for a waiting task
  hint: set lifecycle.queue_depth to 1 or more in murmur.yaml, or launch without --lifecycle-task-acceptance queue

Under single and none, queue_depth has no effect, and any value is accepted. See lifecycle.queue_depth.

A top-level mur_version key is refused whatever its value, including blank, and with or without a deploy: block beside it:

error[E-MAN-003]: murmur.yaml: 'mur_version' is not a top-level key — the mur release a deploy installs is pinned as 'deploy.mur_version'
  hint: move the pin under deploy: as deploy.mur_version — only mur deploy reads it, to choose the mur it installs on the target

See deploy.

Capability errors

E-CAP-001 — invalid network.allow entry

An entry in a capabilities.network.allow list could not be parsed, so the capsule is refused before any registry pull, artifact compile or workdir creation:

error[E-CAP-001]: invalid network allow entry '<entry>': <reason>

The accepted forms are a full URL, a bare host, and a host with a port. A path, query or fragment on the entry, and any scheme other than http/https, are rejected — see Network allow entries.

E-CAP-002 — invalid filesystem.scope

A filesystem.scope value is not a usable subdirectory of the work folder:

error[E-CAP-002]: invalid filesystem scope '<scope>': scope must be relative to the workdir
error[E-CAP-002]: invalid filesystem scope '<scope>': scope cannot escape the workdir via '..'

The same two rules apply wherever a scope is declared: the capsule-wide capabilities.filesystem.scope, a per-hook grant, and a per-tool or per-driver narrowing. Like E-CAP-001, the check runs before any registry pull, artifact compile or workdir creation. scope: "." is the explicit "whole workdir" grant. See Filesystem scope.

E-CAP-003 — declared floor not achievable on this host

If the achieved class is weaker than the effective declared floor, mur run refuses to launch before any registry pull, artifact compile, or workdir creation. The message names both classes and the reason. The hint is chosen by the cause:

Cause Hint
The host lacks the mechanism lower the declared floor to '<achieved>' (capabilities.containment in murmur.yaml, containment in .murmur/config.yaml, or --containment), or run on a host that provides '<declared>'
capabilities.filesystem.workdir_exec: true set capabilities.filesystem.workdir_exec to false, or lower the declared floor to 'advisory' (…the same three places)
The runtime or records directory lies inside the workdir run with a --workdir that does not contain ~/.murmur, or lower the declared floor to 'advisory' (…the same three places)
A declared provider tool needs sealed and the effective floor is below it set `capabilities.containment: sealed` in murmur.yaml (or pass `--containment sealed`); this host can back '<class>' and, when that class is below sealed, so the capsule also needs a host that provides 'sealed'
error[E-CAP-003]: declared containment class 'scoped' is not achievable on this host (achieved: 'advisory'): scoped requires Landlock filesystem mediation (Linux 5.13+ with a usable Landlock ABI); this host provides no kernel filesystem mediation, so paths outside the workdir are constrained by convention only
  hint: lower the declared floor to 'advisory' (capabilities.containment in murmur.yaml, containment in .murmur/config.yaml, or --containment), or run on a host that provides 'scoped'

For sealed the reason names the specific missing piece and the command that fixes it, because "no composed root here" has several completely different causes. Whether sealed is refused is decided by the namespace probe building the composed root inside the capsule's network namespace, never by the AppArmor setting. When a probe attempt failed, the reason has three parts: the class sentence below, the call the probe saw fail, and the readings line E-CAP-005 ends with:

error[E-CAP-003]: declared containment class 'sealed' is not achievable on this host (achieved: 'scoped'): <class sentence and remedy> The probe's <call> failed with <ERRNO> (<description>). Host readings: kernel.apparmor_restrict_unprivileged_userns=<0|1>; AppArmor profile confining mur: <profile>.

When the capsule declares a provider tool, a sealed refusal names each such tool on its own line under the message, before the hint:

error[E-CAP-003]: declared containment class 'sealed' is not achievable on this host (achieved: 'scoped'): <reason>
  required by: tool 'computer' (provider_tool: computer)
  hint: lower the declared floor to 'scoped' (capabilities.containment in murmur.yaml, containment in .murmur/config.yaml, or --containment), or run on a host that provides 'sealed'

A probe that could not run reads The probe could not run: <the runtime's message>. in place of the failed call. A non-Linux host and a host without Landlock fail no attempt, and their reason is the class sentence alone. The runtime never falls back to a weaker class, whichever one it reports:

Reason reported What to do
This platform has no mount namespace and never will Nothing — macOS and every other non-Linux host stay at advisory permanently
AppArmor's unprivileged-userns restriction is active while the mur-sealed profile is not confining this binary. Reported only when the network namespace was refused at unshare or writing uid_map/gid_map with EPERM or EACCES, the setting reads 1 and mur is unconfined Install and load the profile shipped with mur: sudo install -m 644 packaging/apparmor/mur-sealed /etc/apparmor.d/mur-sealed && sudo apparmor_parser -r /etc/apparmor.d/mur-sealed, or re-run the mur installer as root. Running a checkout build out of ./target, which no shipped profile attaches to: sudo scripts/install-dev-apparmor.sh. Last resort, only where a profile genuinely cannot be loaded: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0, which removes unprivileged-userns hardening from every program on the machine and reports W-SEC-013
A user namespace was refused — the usual answer inside a container, where CAP_SYS_ADMIN is absent or the container's own seccomp filter blocks unshare(2) Add --cap-add SYS_ADMIN to the container invocation, or establish the mount namespace outside the container and run mur inside it
The namespace was created but its identity uid_map/gid_map could not be written, so the process cannot own it Check that /proc/sys/user/max_user_namespaces is non-zero, that no LSM policy blocks uid_map writes for this binary, and that mur is not already running inside an unmapped user namespace
The namespace was created but mount(2) inside it was refused — a confinement that permits userns_create and then withholds CAP_SYS_ADMIN Load the shipped profile with sudo apparmor_parser -r /etc/apparmor.d/mur-sealed; inside a container, add --cap-add SYS_ADMIN, or establish the mount namespace outside it
The namespace was created and mounted inside, but pivot_root(2) was refused On an AppArmor host, check the loaded profile carries a pivot_root, rule and reload it; inside a container, pivot_root(2) is commonly absent from the runtime's seccomp allowlist, so establish the mount namespace outside the container
sealed is built inside the capsule's network namespace, and this host could not create or enter it Fix the network namespace first: E-CAP-005 names the step that failed and its remedy
The probe could not run, so whether this host can back sealed is unknown; refused rather than assumed Re-run once the process or descriptor limit or memory is relieved, or allow the probe's syscalls
The kernel provides no unprivileged user namespaces at all (CONFIG_USER_NS=n, or user.max_user_namespaces=0) sudo sysctl -w user.max_user_namespaces=10000 if the sysctl is merely zeroed, otherwise run on a kernel built with CONFIG_USER_NS=y
No usable Landlock ABI, which sealed keeps inside the composed root as defence in depth Run on Linux 5.13+ — a host that cannot back scoped cannot back sealed either

One case names the manifest rather than the host, because no host can satisfy it: capabilities.filesystem.workdir_exec: true declared alongside capabilities.containment: scoped (or sealed). An executable workdir keeps the Landlock Execute right on the work folder, so a binary the capsule compiles, downloads or renames inside it runs regardless of capabilities.shell.allow — the allowlist stops being an enforceable property of the capsule, and the achieved class is capped at advisory on every host. Remove workdir_exec, or lower the declared floor to advisory.

A second case names the workdir: ~/.murmur/sessions, where the session's runtime directory is created, or the session's records directory, lying inside the workdir the capsule's commands write — HOME and --workdir the same directory, for one. The commands could then change the task, MURMUR.md, a submitted plan, an installed tool or the session's trace, and the achieved class is capped at advisory. The reason names the directory and the workdir:

error[E-CAP-003]: declared containment class 'scoped' is not achievable on this host (achieved: 'advisory'): the runtime directory /home/dev/.murmur/sessions and the records directory /home/dev/.murmur/records/home/dev/<session-id> lie inside the workdir /home/dev, so this capsule's commands can change the files the runtime owns — task.md, MURMUR.md, plans and installed tools — and the records it keeps about the session — its trace, logs, eval scores and delegation outcome, and no host can back a class above advisory for it; run with a --workdir that does not contain ~/.murmur
  hint: run with a --workdir that does not contain ~/.murmur, or lower the declared floor to 'advisory' (capabilities.containment in murmur.yaml, containment in .murmur/config.yaml, or --containment)

Run with a --workdir that does not contain ~/.murmur. Below a scoped floor the launch runs and prints W-SEC-033 instead.

A sealed host that clears the probe at launch and then fails to build the composed root for a particular subprocess is a different event and gets its own code, E-RUN-014. Two causes reach it, and the message names the failing step and its errno:

  • A capabilities.shell.staged_runtime grant whose source_path does not exist on this host. Each grant is planned as a required bind ahead of pivot_root, so the mount(2) fails with ENOENT and the session aborts. Fix the path, or drop the grant.
  • Something moved underneath the runtime mid-session — an AppArmor profile reloaded, a container policy changed. Re-probe with mur run --explain-scope.

Neither means the declared floor was wrong; E-CAP-003 covers that.

A provider tool below sealed names the manifest. A capsule declaring a tool whose artifact carries a provider_tool: block needs the sealed floor whatever the host can back. It is decided against the effective declared floor, after the artifacts are read and before the session directory exists or any driver is called; the floor is never raised for it:

error[E-CAP-003]: tool 'computer' (provider_tool: computer) declares a provider tool, which runs only under the 'sealed' containment floor, and the effective containment floor is 'scoped'
  hint: set `capabilities.containment: sealed` in murmur.yaml (or pass `--containment sealed`); this host can back 'sealed' — see https://docs.murmur.nexus/reference/containment/#provider-tools

Every such tool is named. Set the floor to sealed, or remove the tool from artifacts:.

E-IO-004 — HOME cannot hold ~/.murmur

Every launch keeps its runtime directory and its records directory under ~/.murmur. A HOME that cannot hold it is refused, and nothing is written anywhere:

Cause Message
HOME is unset HOME is not set, so there is no ~/.murmur to hold this session's runtime and records directories
HOME is a relative path HOME is '<value>', which is not an absolute path, so there is no ~/.murmur to hold this session's runtime and records directories
~/.murmur, ~/.murmur/sessions or ~/.murmur/records is not a directory this user can write, or ~/.murmur is absent and HOME cannot be written ~/.murmur (<path>) cannot hold this session's runtime and records directories: <reason>
error[E-IO-004]: HOME is not set, so there is no ~/.murmur to hold this session's runtime and records directories
  hint: set HOME to the absolute path of a directory you own, in which ~/.murmur exists or can be created as a 0700 directory
Raised by When
mur run Before any artifact store, lockfile or registry is read; also under --explain-scope
mur eval run When a case is staged
mur trace show, steps, diff, report, list, rm, mur eval show, diff, mur run --resume When the address needs a records group, which list and rm always do; the path form needs none
mur doctor In place of the runtime and records directory paths, counted as a failed check

E-RUN-006 — a harness binary was not found

A transport: process capsule names an executable that is not installed on this host. mur run refuses at staging, before the session directory exists.

error[E-RUN-006]: harness binary 'my-cli', named by the process driver's describe(), was not found on this host
  hint: install the harness this capsule's process driver drives, or point inference.command at the executable to run

The name comes from one of two places, and the message says which:

Named by Resolved as
inference.command A value containing / is used as a path and must be an executable file; a bare name is looked up on PATH
The process driver's describe() Looked up on PATH

The same code covers a transport: http inference driver artifact that is not installed in the local artifact store.

E-RUN-015 — --resume and --context together

mur run --resume <session> resolves a session address to the context id that session ran under, and then runs exactly as --context <id> would. The two name the same thing two ways, so passing both is refused rather than resolved by precedence:

error[E-RUN-015]: --resume and --context name the same thing two ways
  hint: --resume <session> resolves that session's context id for you; --context <id> names one directly. Pass whichever you have, not both — see https://docs.murmur.nexus/reference/cli/

Refused before the session address is resolved and before anything is staged, so no session directory appears.

E-RUN-016 — the resumed session ran no task

--resume reads the context id off the named session's trace.jsonl, from the first task_start line. A session that never reached a task carries none, and there is no conversation to continue:

error[E-RUN-016]: cannot resume session ses_0193f2…: its trace.jsonl records no task_start carrying a context id
  hint: only a session that actually ran a task has a conversation to continue. Run `mur trace show <session>` to see what it did, and resume one that reached a task — see https://docs.murmur.nexus/reference/cli/

Run mur trace show <session> to see what that session did.

E-RUN-017 — the resumed context kept no record

The context id --resume resolved has nothing on disk to continue. Resuming it would start a fresh conversation while reporting success, so it is refused instead, naming the session, the context and which of the reasons applies:

error[E-RUN-017]: cannot resume session ses_0193f2…: context 'ctx_0193f2…' has no conversation record (no conversation record at /home/you/.murmur/conversations/shey/ctx_0193f2…/conversation.jsonl)
  hint: a session is resumable only if its capsule kept something to continue it with: a conversation record under transport: http, or a harness-session.json under transport: process. Either way context.record: off keeps neither. Run `mur trace show <session>` to see what that session did, and omit --resume to start a fresh conversation — see https://docs.murmur.nexus/reference/cli/

What "nothing to continue" means is the one thing the transport decides:

Transport What is looked for
http The context's conversation.jsonl
process The context's harness-session.json, which holds the session id the harness answers to

The other reasons apply to both: context.record: off, a capsule with no inference: block, and a host whose home directory cannot be resolved. Refused at staging, before this launch's session directory is created.

E-RUN-018 — --resume-mode compact with no compaction hook

--resume-mode compact runs the capsule's on-compaction hook over the loaded record and continues from its summary. With nothing bound to that event there is nothing to produce the summary, and quietly serving full instead would give the operator a mode they did not ask for:

error[E-RUN-018]: --resume-mode compact needs a hook bound to on-compaction; this capsule declares none
  hint: declare a hook artifact whose binding is on-compaction (or all) with commit_policy: replace-context, or use --resume-mode full, which loads the record verbatim and needs no hook — see https://docs.murmur.nexus/reference/cli/

--resume-mode full is often the cheaper mode anyway: a verbatim reload can hit the provider's prompt cache, while compaction changes the prefix from the first altered token, guarantees a cache miss, and costs an extra inference call to produce the summary.

E-RUN-019 — the session could not register with mur-roost

A capsule whose manifest declares capabilities.spawn.allow is refereed by mur-roost every time it asks to spawn something, and the daemon can only referee a session whose grants it holds. The session announces itself at launch (see POST /register); a registration it cannot complete refuses the launch rather than leaving a capsule that can delegate running outside the knowledge of the daemon that bounds it:

error[E-RUN-019]: failed to register this session with mur-roost at http://127.0.0.1:7700: failed to connect to 127.0.0.1:7700: Connection refused (os error 111); a capsule declaring capabilities.spawn.allow must be known to the daemon that referees its spawns, so the launch is refused rather than run unrefereed
  hint: start the daemon the capsule registers with — `mur-roost --port 7700 --registry-path <store>` — and set MURMUR_ROOST_URL to its base URL, or drop capabilities.spawn.allow from the manifest if this capsule spawns nothing

MURMUR_ROOST_URL unset or blank reports the same code with MURMUR_ROOST_URL is not set as the reason. The message names the daemon and the reason, never a token.

mur doctor reports the same three states — mur-roost not installed, MURMUR_ROOST_URL not set, and nothing answering at the URL it names — for a capsule declaring capabilities.spawn.allow, so the refusal is visible before a run meets it. See Start the daemon for how to obtain mur-roost and start it.

A capsule that declares no spawn capability never reaches this — it opens no connection to the daemon at all, and mur run succeeds with nothing listening.

E-RUN-020 — the spawner handle or spawner lifeline could not be read

A capsule launched as a delegated child is handed MURMUR_SPAWNER, which names where its outcome is reported and under which delegation id (see The completion path). A child that cannot read it can tell nobody that it finished, so the launch is refused before a session is staged:

error[E-RUN-020]: MURMUR_SPAWNER does not carry a readable spawner handle: it is not JSON: expected value at line 1 column 1; a delegated child must be able to tell its spawner that it finished, so the launch is refused rather than run unreportable
  hint: MURMUR_SPAWNER is injected by a parent capsule's runtime at launch; unset it to run this capsule directly

The reason names what about the value could not be read — that it is not JSON, that a field is missing, or that trust is not a trust class — and never the value itself, which names another capsule's address and session.

An unset or blank MURMUR_SPAWNER is not this error: it is the ordinary case of a capsule nobody delegated, which reports to nobody and runs exactly as it would have.

The same code refuses a MURMUR_SPAWNER_LIFELINE that does not name this session's spawner lifeline, before any session directory exists. A child that cannot hear its spawner would outlive it:

error[E-RUN-020]: MURMUR_SPAWNER_LIFELINE does not carry this session's spawner lifeline: descriptor 9 is a regular file, and a lifeline is a pipe's read end; a delegated child that cannot hear its spawner would keep running after the session that delegated to it has ended, so the launch is refused
  hint: MURMUR_SPAWNER_LIFELINE is set by a parent capsule's runtime for each child it launches, beside MURMUR_SPAWNER, and is not for operators; unset it to run this capsule directly
Reason The value
it is not a decimal descriptor number Holds anything but digits
it is not a descriptor number this platform has Is too large to be a descriptor
descriptor N is a standard stream, and a lifeline is 3 or above Is 0, 1 or 2
descriptor N is not open Names no open descriptor
descriptor N is a character device / a regular file / a directory / a socket / not a pipe, and a lifeline is a pipe's read end Names something other than a pipe
descriptor N is open for writing, and a child holds only a read end Names a pipe's write end
this session was launched by a caller that does not own its process, and a lifeline ends the process Reached a launch embedded in another program rather than mur run

A blank MURMUR_SPAWNER_LIFELINE is absent. The variable needs no MURMUR_SPAWNER and no MURMUR_FORMATION_ID beside it.

E-RUN-021 — the native binary is built for another platform

A native tool artifact carries a host executable at bin/<name>. Staging compares the platform that binary was built for against this host's, before any of the session's native binaries is written to the workdir:

error[E-RUN-021]: native tool 'tool-git' cannot run on this host: its binary is built for darwin-aarch64, this host is linux-x86_64
  hint: the installed artifact holds a binary for another platform — reinstall it on this host with `mur install <name>@<version>`, or publish a build for this host's platform

The check reads the binary's header and recognises these formats:

Format Identified from Platforms
ELF64 e_machine linux-x86_64, linux-aarch64
Mach-O 64-bit cputype darwin-x86_64, darwin-aarch64
Fat Mach-O 0xCAFEBABE magic any darwin-* host

Architecture is compared as strictly as operating system: an x86_64 ELF on an aarch64 Linux host is refused on the same terms as a Mach-O on Linux.

Refusal requires a positive identification of both sides. A payload in a format the check does not recognise — a shell script, a WASM module, a 32-bit image — stages and runs, and so does any payload on a host outside the four platform targets.

When a capsule declares several native tools, one unrunnable binary refuses all of them: a refused session leaves no tool binaries in its workdir.

mur doctor reads the same header of the same installed bytes and fails that artifact's line, so the mismatch is reportable without launching a session.

E-RUN-022 — the address names no running capsule

mur watch and mur cancel resolve a session address against the running-capsule records on this machine. This is the refusal when none of them names a capsule that is still there.

error[E-RUN-022]: ses_019f01a940ce7761854e768ecbe3d399 is not running: no process holds pid 48213
  hint: its record has been removed; start the capsule again to make the address resolve

The record naming a session whose process is gone is removed by the read that reported this, so a second attempt reports that nothing is running rather than naming the same session twice:

error[E-RUN-022]: no capsule is running on this machine
  hint: start a capsule with `mur run`; it is addressable for as long as it serves

The reason names which of the two local checks the record failed.

Reason Means
no process holds pid N Nothing is running under that process id
pid N is held by a process that started at another time The process id was handed out again to something unrelated

The same code covers an address that is not a session address at all — a path, a host:port, or fewer than four characters:

error[E-RUN-022]: 'localhost:41235' is not a session address
  hint: name a running session with @1, a ses_ id, or a 4-character suffix of one

To reach a capsule by address rather than by name, pass --url <host:port>.

mur stop <formation-id> refuses with this code, and signals nothing, when the formation id is malformed or names no running member:

error[E-RUN-022]: no member of formation frm_019f01a93ff27c1e9a3b5d0c4e8f2a61 is running on this machine
  hint: `mur ps` lists the formations running here
Message Means
'<arg>' is not a formation id: … The argument starts frm_ and is not a whole formation id
no member of formation <id> is running on this machine No record names that formation
formation <id> is not running: N member record(s) named processes that are gone, and were removed Every record naming it was stale

E-RUN-023 — the capsule did not answer

The address named a capsule whose process is alive, and the capsule's agent card did not come back:

error[E-RUN-023]: ses_019f01a940ce7761854e768ecbe3d399 is running but its capsule did not answer at localhost:41235: failed to connect to localhost:41235: Connection refused (os error 111)
  hint: the process holding the door is alive — it may be suspended or the host overloaded; its record is kept

E-RUN-022 removes the record; this one keeps it. The process is running, so the record still names something real, and a capsule that was slow to answer must not lose the only handle anyone has on it. Try again, or read what the session is doing with mur trace show in its workdir.

Reason Means
failed to connect to …, or a read timeout The door did not answer in time — the process may be suspended, or the host too loaded to run it
the capsule at … answers for session … Another session holds that address
the agent card from … names no session The capsule was started by mur v0.4.0 or earlier, whose agent card this version cannot read a session from. It stays unreachable until it ends; mur stop still ends it
pid N's start time could not be read A process holds the process id and the host would not say when it started, so whether it is the capsule is unknown

E-RUN-024 — the session could not be ended

Raised by When
mur stop The session or formation could not be ended
mur trace rm The session to remove is still running

mur stop could not end the process a running-capsule record names: a signal was refused, or the process is still there afterwards.

error[E-RUN-024]: ses_019f01a940ce7761854e768ecbe3d399 could not be ended: SIGTERM to pid 48213 was refused: Operation not permitted (os error 1)
  hint: the capsule is still running and its record is kept; the process may belong to another user

The message names which signal was refused and why.

Reason Means
SIGTERM to pid N was refused: pid N's start time could not be read A process holds the process id and the host would not say when it started, so it could not be confirmed as the capsule and was not signalled
SIGTERM to pid N was refused: … with an OS error The process is there and this user may not signal it — usually another user's capsule
SIGKILL to pid N was refused: … The same two, at the escalation step
pid N was still running 5 seconds after SIGKILL The kernel accepted the signal and the process has not gone, which an uninterruptible wait can cause

The record is not removed. Unlike E-RUN-022, the capsule it names is still running, so unlinking the record would remove the only handle anyone has on it. Every task the session held was cancelled before the signalling started, so the capsule is idle even though it is still there.

mur stop <formation-id> reports a formation it could not end with the same code, as formation <id> could not be ended: <reason>:

Reason Means
its members record N different launchers (pid A, pid B) The members disagree about which process launched them; nothing was signalled. End each member with mur stop <session>
SIGTERM to its launcher, pid N, was refused: … The launcher could not be confirmed or may not be signalled by this user; nothing was signalled and every member is still running
SIGKILL to its launcher, pid N, was refused: … The same, at the escalation step
its launcher, pid N, was still running 5 seconds after SIGKILL The kernel accepted the signal and the launcher has not gone
N of its members were still running 25 seconds later: <session>, … Those members' processes outlived the wait; their records are kept

A member started by hand that could not be ended is reported as error: … in the report and counted among the members still running.

mur trace rm refuses a session that a running-capsule record names while its process is alive, or alive with a start time that could not be read, and removes nothing:

error[E-RUN-024]: session ses_019f0193c7d871a5b2e30ff41a7c0ce1 is still running (pid 48213); nothing was removed
  hint: stop it with `mur stop ses_019f0193c7d871a5b2e30ff41a7c0ce1` first, then remove it

E-RUN-025 — an artifact with a gateway declares no usable upstream_auth: block

An artifact entry declares gateway:, and the artifact's own murmur.yaml does not say how its upstream takes the key. The runtime attaches the key to each of the artifact's gateway requests itself, so without that declaration it cannot, and mur run refuses at staging, before any component runs or any request is sent. The refusal applies whether or not gateway.api_key is set, and to the inference driver, tools and hooks alike.

error[E-RUN-025]: artifact 'murmur/driver-anthropic@0.19.0' declares no usable upstream_auth: block
  hint: the runtime presents the key itself and needs the artifact to say how; update the artifact to a version whose murmur.yaml declares upstream_auth:, or remove gateway: from its entry

A block that is present and malformed adds the reason in parentheses:

Reason names Means
field 'inference_auth' was renamed to 'upstream_auth' The artifact declares the block under its former name. Nothing under that name is read; update the artifact to a version that declares upstream_auth:
'upstream_auth' has invalid type The block is not a mapping
missing required field 'upstream_auth.header' or 'upstream_auth.value' A child is absent
'upstream_auth.header' has invalid type or 'upstream_auth.value' has invalid type A child is not a string
is not a valid HTTP header name header cannot be sent as a header
cannot carry the key header names a header that routes or frames the request, or that upstreams commonly echo back, such as Host or Origin
must contain {key} exactly once value has no {key}, or more than one
contains characters an HTTP header value cannot hold value has a control character such as a newline

The block's shape is in upstream_auth: block.

E-RUN-026 — the spend ledger cannot be used

spend.machine_tokens_per_day is set, and the shared ledger the machine ceiling is counted in cannot be used: HOME is unset, ~/.murmur/spend cannot be created or held as a 0700 directory, or today's file cannot be opened. mur run refuses a transport: http capsule at staging, before the session directory exists and before any provider request.

error[E-RUN-026]: spend ledger at /home/me/.murmur/spend is unavailable: failed to create the directory: File exists (os error 17)
  hint: spend.machine_tokens_per_day is set in config.yaml, and the machine spend ceiling cannot be kept without its ledger; make ~/.murmur/spend a directory this user can write, or remove spend.machine_tokens_per_day

E-RUN-027 — the provider rejected the inference credential

The provider answered a transport: http inference request with 401, and re-reading the credential did not cure it. When a request is rejected, the runtime re-reads credentials.<NAME> at once. If the value there has changed, the request is sent once more with it. The task ends with this error when the value is unchanged, or when that single resend is rejected too. It replaces the driver's own error text. The same message, without the code, is written to out/result.txt in the session's records directory and to a task_failed line with cause: "credential_rejected", and the task fails: mur run then also prints E-RUN-040 carrying the same message, and exits 1.

error[E-RUN-027]: the provider rejected the inference credential credentials.ANTHROPIC_API_KEY in /home/me/.murmur/config.yaml (HTTP 401)
  hint: replace it with `mur config set -g credentials.ANTHROPIC_API_KEY <key>`; running capsules use it on their next call

What the message names depends on where the key came from:

Source Message names Hint
credentials.<NAME> in the global config The entry and the config file path Replace the entry; running capsules use it on their next call
The environment variable <NAME> The variable, and that it is read once at launch Restart the capsule with a valid key, or store the key as credentials.<NAME>
A literal gateway.api_key on the driver's entry in murmur.yaml The manifest literal, and that it is read once at launch As for the environment

When the single resend was also rejected, the message adds that the credential was re-read and its new value was rejected too. The session trace records the rejection as an inference_credential event with change: "rejected". Any status other than 401, including 403, reaches the driver unchanged and is reported as the driver reports it.

E-RUN-028 — the running-capsule records could not be read

mur ps, mur watch, mur cancel, mur stop, mur ps --json, mur trace list and mur trace rm read the running-capsule records under ~/.murmur/running/, and that directory could not be created, held at 0700, or listed. mur ps --json, mur trace list and mur trace rm only list it, and also report a HOME from which it cannot be located. Which capsules are running is unknown, so nothing is listed, nothing is removed and nothing is signalled.

error[E-RUN-028]: the running-capsule records could not be read: /home/me/.murmur/running: failed to create the directory: File exists (os error 17)
  hint: nothing was listed and nothing was removed; check that ~/.murmur/running is a directory this user owns

The message ends with the path and the operating system's error.

Error Means
File exists, Not a directory A file is where the directory belongs
Permission denied The directory exists and this user may not list it

E-WIT-LEVEL-001 — an artifact does not fit the served WIT level

An artifact fits the WIT level this mur serves when every murmur:* interface it names is at the version the level lists for its package. Two commands refuse one that does not:

Command Refuses
mur install A version it resolves by name, before anything is fetched or written: <scope>/<name>@<version>, --all-platforms <scope>/<name>@<version>, and each version murmur.yaml pins when run with no argument, whether its payload would come from the project store, the global store or the scope's source
mur run, mur eval, mur new A driver, at staging, before any driver runs

mur install ./<file>.mur.zip, mur install github:<owner>/<repo>@<tag>, mur publish and mur deploy install the bytes as given; a launch refuses a driver among them that does not fit. A version left out of mur install <scope>/<name> is always the newest that fits.

A runtime pull — manage.pull() or the pull-artifact tool — refuses a version that does not fit before anything is written, as a tool error or a manage.pull error opening does-not-fit:. Its hint names the newest version that fits in the places the pull may use, or says none does:

does-not-fit: [E-WIT-LEVEL-001] artifact 'murmur/tool-edit@2.0.0' does not fit WIT level 3, the level this mur serves: it imports murmur:tool-registry/invoke@9.9.9, and level 3 has murmur:tool-registry@0.2.0; murmur/tool-edit@1.0.0 is the newest version in the global store that fits level 3
Situation Hint
An allowed place holds a version that fits <scope>/<name>@<V> is the newest version in <the project store \| the global store \| github:<owner>/<repo>> that fits level <N>
None does no version of <scope>/<name> in <places> fits level <N>

At staging:

Driver Refused when
Every runtime: driver artifact the manifest declares, except a transport: process inference driver It imports any murmur:text interface, or a murmur:stream interface at another version
The inference.driver artifact of a transport: process capsule It exports murmur:driver/process only at another version

The message names the artifact, the served level, and one clause per interface that does not fit:

error[E-WIT-LEVEL-001]: artifact 'ana/tool-x@2.0.0' does not fit WIT level 3, the level this mur serves: it names murmur:tool/run@9.9.9, and level 3 has murmur:tool@0.2.0
  hint: ana/tool-x@1.0.0 is the newest version in github:ana/artifacts's index that fits level 3; pin it in murmur.yaml and run `mur install`, or run `mur install ana/tool-x@1.0.0`
error[E-WIT-LEVEL-001]: artifact 'ana/process-driver@1.0.0' does not fit WIT level 3, the level this mur serves: it exports murmur:driver/process@0.3.0, and level 3 has murmur:driver@0.4.0

A clause reads it exports|imports <interface>, and level <N> has <package>@<version>, or …, and level <N> has no version of <package> for a package the level does not list. When mur install refuses a version from the source's index rather than from a stored payload, the clause reads it names <interface>: the index lists a version's interfaces without saying which it exports and which it imports.

The hint names the newest version of the artifact in its scope's source index that fits the served level. Finding it downloads no payload:

Situation Hint
The index lists a version that fits <scope>/<name>@<V> is the newest version in <source>'s index that fits level <N>; pin it in murmur.yaml and run `mur install`, or run `mur install <scope>/<name>@<V>`
The index lists none that fits no version of <scope>/<name> in <source>'s index fits level <N>; rebuild it against level <N>'s packages (`mur version` lists them)
The index could not be read the index of <source> could not be read (<reason>), so no fitting version can be named; run `mur search <scope>/<name>` once it answers, or rebuild it against level <N>'s packages (`mur version` lists them)
No source serves the scope no source serves the '<scope>' scope, so no index can name a version of <scope>/<name> that fits level <N>; add the repository it publishes from under registry.sources and run `mur search <scope>/<name>`, or rebuild it against level <N>'s packages (`mur version` lists them)
The artifact was installed from a file (local/) local/<name> was installed from a file, so no source's index can name a version of it that fits level <N>; rebuild it against level <N>'s packages (`mur version` lists them), then run `mur install <file>` on the rebuilt file
  hint: ana/process-driver@1.1.0 is the newest version in github:ana/drivers's index that fits level 3; pin it in murmur.yaml and run `mur install`, or run `mur install ana/process-driver@1.1.0`
  hint: no version of ana/process-driver in github:ana/drivers's index fits level 3; rebuild it against level 3's packages (`mur version` lists them)
  hint: the index of github:ana/drivers could not be read (request failed: io: Connection refused), so no fitting version can be named; run `mur search ana/process-driver` once it answers, or rebuild it against level 3's packages (`mur version` lists them)
  hint: no source serves the 'ana' scope, so no index can name a version of ana/process-driver that fits level 3; add the repository it publishes from under registry.sources and run `mur search ana/process-driver`, or rebuild it against level 3's packages (`mur version` lists them)
  hint: local/process-driver was installed from a file, so no source's index can name a version of it that fits level 3; rebuild it against level 3's packages (`mur version` lists them), then run `mur install <file>` on the rebuilt file

mur doctor lists every installed artifact that does not fit, under its WIT level block.

E-RUN-029 — the driver does not speak the driver interface

mur run refuses at staging, before any driver runs, when a driver names no interface it could be served by at any level:

Driver Refused when Hint
The inference.driver artifact of a transport: process capsule It exports no version of murmur:driver/process, so it is not a process driver rebuild the driver against murmur:driver/process@0.4.0, or name a process driver
Every runtime: driver artifact the manifest declares, except a transport: process inference driver It imports a murmur:stream interface at the served package version under a name the package does not define, such as murmur:stream/other@0.1.0 rebuild <name> against murmur:stream/events@0.1.0, the stream interface this mur serves; pin a release built against it in murmur.yaml and run `mur install`
error[E-RUN-029]: artifact 'my-process-driver@1.0.0' is the transport: process driver but does not export murmur:driver/process@0.4.0; a process driver must be built against the process-driver world
  hint: rebuild the driver against murmur:driver/process@0.4.0, or name a process driver

A driver at another version of either interface is E-WIT-LEVEL-001, which is reported when a driver fails both checks.

E-RUN-030 — the http driver is a process driver

A transport: http manifest names an artifact under inference.driver that exports the process driver interface. mur run refuses at staging, before the driver runs.

error[E-RUN-030]: artifact 'my-process-driver@1.0.0' is the transport: http inference driver but exports murmur:driver/process@0.4.0, the process driver interface; name it under transport: process, or name an http driver
  hint: set inference.transport: process to use this driver, or name an http driver

E-RUN-032 — the process driver could not be loaded

A process driver is loaded with no grants: no environment, no files, no network and no Murmur host interface. mur run refuses at staging when the driver:

  • imports anything but WASI,
  • fails or runs out of time while describing itself, or
  • names a required variable that is empty or contains =.

The message ends with the reason; for an import, it names the interface.

error[E-RUN-032]: process driver 'my-process-driver@1.0.0' could not be loaded with no grants: component imports instance `murmur:stream/events@0.1.0`, but a matching implementation was not found in the linker
  hint: the driver must export murmur:driver/process@0.4.0 and import nothing but WASI

E-RUN-033 — a harness turn failed

A transport: process turn ended in failure. The kind names what went wrong, and the message is the harness's own.

error[E-RUN-033]: the harness turn failed (auth): not signed in on this host
  hint: see `mur trace show` for the turn; auth means the harness is not signed in on this host, quota means wait or raise the plan's limit, and max-turns means raise inference.max_turns
Kind Means
auth The harness could not authenticate
quota The harness ran out of quota, or was rate limited
max-turns The turn limit was reached — the harness's own, or inference.max_turns
canceled The turn was interrupted
harness-error The harness reported an error of its own
other Anything else the harness reported

The trace carries the same failure as a harness_failed event, whose source says whether the harness reported it (harness) or the runtime ended the turn itself (runtime, which is what a turn past inference.max_turns is). mur trace show prints it as one line.

E-RUN-034 — a process driver call failed

A call into the process driver refused, crashed, or ran out of time. The message names the WIT function and what came back.

error[E-RUN-034]: process driver 'my-process-driver@1.0.0' failed in launch: launch refused by config
  hint: the driver refused or failed to translate for its harness; check inference.driver.config, or use a driver release built for this harness version
Call Made
launch Once per run, to plan the harness's arguments, environment, files and stdin
parse For each batch of complete stdout lines
classify-exit When the harness's output ended without a terminal event

A driver that runs past its 30-second per-call deadline, or whose launch plan names an unusable environment variable or file, fails the same way.

E-RUN-035 — the harness went silent

A transport: process run went 600 seconds with neither a line of harness output nor a tool call through the bridge, so the harness was killed.

error[E-RUN-035]: the harness produced neither a line of output nor a tool call for 600s and was killed
  hint: the harness was waiting on something that never came; check `mur trace show` for the last thing it did

There is no limit on how long a run may take: any output, and any tool call, starts the window again. Output on stderr does not. The trace's harness_exit event records cause: "inactivity".

The 600 seconds is fixed, as is the 10-second interrupt grace a cancelled harness gets. Neither is a manifest setting.

E-RUN-036 — the harness could not find the session

A turn on transport: process was launched to continue the session this context's harness session map holds, and the harness failed it without ever reporting that session. The conversation is gone from the harness's own store:

error[E-RUN-036]: the harness 'claude-code' could not continue session 0199c7d4-1f60-7c31-9a6e-0f2b9c1d4e55, which context 'ctx_0193f2…' names: no conversation found with session id 0199c7d4-1f60-7c31-9a6e-0f2b9c1d4e55 (that id is recorded in /home/you/.murmur/conversations/shey/ctx_0193f2…/harness-session.json)
  hint: the harness no longer holds that conversation. murmur left the id where it is, so the next task in this context fails the same way rather than answering from nothing. Ask for it to be dropped and this context starts a new conversation: `mur run --context <id> --forget-session`, or `"forgetSession": true` under the murmur door extension's key in the next SendMessage or SendStreamingMessage message's metadata — see https://docs.murmur.nexus/reference/agent-card/#forget-session

The map entry is left exactly as it was. Starting a new conversation instead would be silent memory loss: the next message would be answered as if nothing had been said. Dropping the entry is a person's decision, asked for on the request that starts the next turn — with mur run --forget-session or the door's forgetSession message field, both described under what removes an entry. A forget asked of a capsule on any other transport is E-RUN-039.

The turn must have been launched to continue a session, the harness must never have reported one, and the failure kind must be harness-error or other. A turn that started a new conversation had nothing to lose, and auth, quota, max-turns and canceled each name a cause of their own — an expired login is not a missing conversation — so all of those stay E-RUN-033.

E-RUN-037 — --resume-mode compact under transport: process

--resume-mode compact runs the capsule's on-compaction hook over the conversation and continues from its summary. On transport: process the harness holds the conversation, so there is no history to hand that hook:

error[E-RUN-037]: --resume-mode compact is not available under inference.transport: process; the harness holds this conversation and murmur has no history to compact
  hint: use --resume-mode full, which hands the harness the session id this context already has and lets it answer from everything it was told — see https://docs.murmur.nexus/reference/cli/

--resume-mode full is what this transport resumes with: the harness is launched on the session id the map holds and answers from everything it was told before.

E-RUN-038 — a spend ceiling against a driver that reports no usage

A transport: process spend ceiling is enforced against the token counts the harness reports and its driver relays. A driver whose describe() sets reports-usage: false relays none, so a ceiling set against it could never be reached. mur run refuses at staging, before anything is spawned and before a session directory exists:

error[E-RUN-038]: process driver 'my-process-driver@1.0.0' reports no usage — its describe() sets reports-usage: false — so inference.max_session_tokens and spend.machine_tokens_per_day could never be enforced
  hint: use a driver release that reports its harness's token usage, or remove the ceiling — a ceiling murmur can never measure against would let the run go on for ever

The message names whichever ceilings are in effect: inference.max_session_tokens from the manifest, spend.machine_tokens_per_day from the effective config, or both. The same capsule with neither in effect runs normally — a driver that reports no usage is a supported driver, and its turns simply carry no token counts.

E-RUN-039 — nothing to forget on this transport

--forget-session and the door's forgetSession message field drop the harness session a context names. Only transport: process has one — on every other transport the runtime holds the conversation itself, in a conversation record. The CLI refuses at staging, before the launch creates anything:

error[E-RUN-039]: --forget-session forgets the harness session a context names, and only inference.transport: process has one; this capsule's transport is 'http'
  hint: a harness owns the conversation only under inference.transport: process, so that is the only transport with a session to forget; every other one keeps its conversation record here, which `mur conversation rm` removes — see https://docs.murmur.nexus/reference/cli/

The door refuses forgetSession: true on SendMessage and SendStreamingMessage with JSON-RPC -32602, and starts no task — see forgetSession.

E-RUN-040 — the task did not complete

The session ran, shut down cleanly, and the task it ran for ended without completing. mur run prints the task's exit status on its status: line, prints this error and exits 1:

status:  failed
error[E-RUN-040]: the task ended failed: {"error":"driver: failed to parse Anthropic response JSON: EOF while parsing a string at line 1 column 38","stop_reason":"error"}
  hint: the task's result text is in out/result.txt in the session's records directory, and `mur trace show <session>` shows the turn it ended on and why
Exit status Reason printed
failed The reason of the run's task_failed line, whose cause names the kind of failure
max_turns_reached That the task used every turn inference.max_turns allows
spend_ceiling_reached That a spend ceiling refused the next inference call. The spend_ceiling_reached line names which ceiling and the numbers
canceled That the task was canceled, by CancelTask, murmur.StopSession, mur stop, SIGTERM or the end of the session that delegated it. A task canceled by its formation's end is no error: the member ends formation_ended

For canceled the hint says there is no result to read:

status:  canceled
error[E-RUN-040]: the task ended canceled: the task was canceled
  hint: the task was canceled before it completed, so out/result.txt in the session's records directory holds no result; `mur trace show <session>` shows the turn it was canceled on and why

A launch reports the first task that did not complete; a later run that completed does not replace it. See Status and exit code. Under --json no status: line is printed, and the error and exit code are the same.

E-RUN-041 — the control token could not be written

A capsule whose manifest declares control: writes its control token to ~/.murmur/running/<session_id>.control before its door is announced. When that file cannot be written, the capsule does not run:

error[E-RUN-041]: this capsule declares control: and its control token could not be written: <path>: <reason>
  hint: make ~/.murmur/running a directory this user can write, or remove control: from murmur.yaml — a capsule whose declared controllability no controller could reach does not run

No inference call is made. The message names the path and the failure, never the token.

E-RUN-042 — no control surface, or a refused control request

mur control prints this in two cases:

Case Message
The session holds no control token, because its manifest declares no control: block <session_id> has no control surface: it holds no control token, because its murmur.yaml declares no control: block
The control surface answered anything but 200 the control surface of <session_id> refused <METHOD> <path>: HTTP <status>: <reason>

What is controllable is fixed at launch, so the first case is answered by declaring control: and restarting the capsule. Neither message carries the token or a secret value.

E-RUN-043 — a runtime pull declared in an operator-only role

A murmur.lock entry with origin: runtime was written by a capsule's manage.pull(), not by an operator command. Such a pin stages only as a tool or a skill without a gateway: block, reached by the model as a marked tool-array entry. mur run refuses the launch, before any session directory exists, when murmur.yaml gives it one of these roles. When more than one applies, the first in the table is named:

Named in the message Where murmur.yaml declares it Hint's second remedy
runtime: hook The artifact's entry remove runtime: hook from its murmur.yaml entry
runtime: driver The artifact's entry remove runtime: driver from its murmur.yaml entry
gateway: The artifact's entry remove gateway: from its murmur.yaml entry
inference.system_prompt_artifact inference:, naming the skill as the system prompt stop binding it as inference.system_prompt_artifact in murmur.yaml
error[E-RUN-043]: murmur.lock pins 'ana/some-tool@1.2.3' from a runtime pull by session ses_0190a1b2c3d4..., and murmur.yaml declares it with gateway:, which only an operator-declared pin may carry
  hint: run `mur install ana/some-tool@1.2.3` to adopt the pin as operator-declared, or remove gateway: from its murmur.yaml entry
error[E-RUN-043]: murmur.lock pins 'ana/some-skill@1.0.0' from a runtime pull by session ses_0190a1b2c3d4..., and murmur.yaml declares it with inference.system_prompt_artifact, which only an operator-declared pin may carry
  hint: run `mur install ana/some-skill@1.0.0` to adopt the pin as operator-declared, or stop binding it as inference.system_prompt_artifact in murmur.yaml

A mur run --system-prompt override replaces the binding, so an overridden launch stages the skill as an ordinary marked entry.

mur eval run refuses the same pin per case: the case is recorded as stage_failed with this message.

mur install <name>@<version> rewrites the entry as origin: operator and the next launch stages it. mur doctor reports the same refusal ahead of a run.

E-RUN-044 — the formation id could not be read

A session launched as a member of a formation is handed MURMUR_FORMATION_ID, and it hands the same value to every child it delegates to. A formation id is frm_ followed by 32 lowercase hex digits. Any other value refuses the launch before a session directory exists:

error[E-RUN-044]: MURMUR_FORMATION_ID does not carry a formation id: it does not start with 'frm_'; a session that cannot tell which formation it belongs to would be grouped with the wrong one, so the launch is refused
  hint: MURMUR_FORMATION_ID is set by the runtime that launches a formation's members and by a member's runtime for the children it spawns; unset it to run this capsule on its own

The reason names what is wrong — the prefix, the number of digits, or a character outside lowercase hex — and never the value itself.

An unset or blank MURMUR_FORMATION_ID is not this error: it is the ordinary case of a session in no formation.

E-RUN-045 — a formation member did not come up

mur run --roster starts every peer and waits for each one's door to answer. A peer that does not come up refuses the whole launch. Every member already started is stopped, the entry member is never started, and no formation line is printed:

error[E-RUN-045]: the formation could not be launched
  'broken' (broken@0.1.0): its process exited with status 1 before reporting; its last stderr lines were:
      [capsule-runtime] warning[W-SEC-027]: artifact 'murmur/driver-anthropic' gateway.api_key is written literally in murmur.yaml, so the key is read once at launch and this capsule cannot pick up a rotated key until it is restarted; store the key with `mur config set -g credentials.<NAME> <key>` to have it re-read (https://docs.murmur.nexus/reference/diagnostics/#w-sec-027)
      error[E-RUN-008]: missing artifacts: murmur/driver-anthropic@9.9.9
        hint: run `mur install` to install all manifest dependencies
  stopped: coder (pid 529088), reviewer (pid 529091)
  hint: the formation was not launched, and nothing it started is still running; fix the member named above and launch again. `mur run --capsule <capsule> --capsule-version <version>` runs one member on its own and shows its whole output

Each failed member is named as '<name>' (<capsule>@<version>), in roster order, with one reason:

Reason Means
its process could not be started: <why> The mur binary could not be executed, or <why> is its directory <dir> could not be made: …: the peer's directory could not be made, or something is already at its path
its process exited with <status> before reporting The member's mur run refused or crashed before its readiness line. Its last stderr lines follow, and carry the member's own error code
its process reported and then exited with <status> before its door answered The member reported a door and ended before it answered
it did not report within 180s No readiness line within the deadline
its first line is not a readiness line The member printed something other than a JSON readiness line first
it reported no door The readiness line names no URL
it reported formation <x> instead of <minted>, or it reported no formation instead of <minted> The member is not running in the formation it was launched into
its door at <url> did not answer as session <ses> within 180s Nothing answered at the reported address, or something else did. The last probe's error follows

stopped: lists the members that were running when the launch was refused, as name (pid N), or none. Every one of them has been stopped and reaped.

E-RUN-046 — not a formation's launcher, or an unreadable formation channel or lifeline

A process that already carries MURMUR_FORMATION_ID is a formation member, and a member cannot launch a formation:

error[E-RUN-046]: MURMUR_FORMATION_ID is set, so this process is already a member of a formation, and a formation member cannot launch a formation
  hint: a member joins the formation it was launched into and never starts one; unset MURMUR_FORMATION_ID to launch this roster as a formation of its own

The same code refuses a member whose formation channel cannot be read, before a session directory exists. The reason names the problem and never quotes the line:

error[E-RUN-046]: MURMUR_FORMATION_CHANNEL does not carry this member's formation credentials: descriptor 987 is not open; a member that cannot read what its launcher handed it could neither prove who it is nor call anyone, so the launch is refused
  hint: MURMUR_FORMATION_CHANNEL is set by `mur run --roster` alone, for each member it starts, beside MURMUR_FORMATION_ID; launch the formation with `mur run --roster`, or unset both to run this capsule on its own
The reason says Means
it is set, but MURMUR_FORMATION_ID is not MURMUR_FORMATION_CHANNEL without a formation id
it does not name a file descriptor by number The variable is not a descriptor number
descriptor N is standard input, output or error, never a channel The variable names 0, 1 or 2
descriptor N is not open Nothing is open at that descriptor
descriptor N is neither a pipe nor a file The descriptor is a socket, a terminal or a directory
the channel ended before its first line arrived The channel was closed empty
no first line arrived within 10s The channel stayed open and silent for 10 seconds
its first line is not JSON, … is not a JSON object, … has no string <field>, … has no calls array The first line is not a member's credentials
its first line's formation_id is not a formation id: … As E-RUN-044 for the id
its first line's member is not a member name: … The name does not match ^[a-z][a-z0-9-]{0,31}$
its first line's verify_key is not a formation key: … The key is not 32 bytes of base64url
its first line's calls[N].name …, calls[N] names a member a second time A callee name is not a member name, or is listed twice
its first line's calls[N].token was not issued to this member for that callee under the line's own key A token does not verify as this member's for that callee
its first line names another formation than MURMUR_FORMATION_ID The channel belongs to another formation

MURMUR_FORMATION_PEERS in mur run's own environment is refused the same way. The runtime sets it inside a member's components, never in a process environment:

error[E-RUN-046]: MURMUR_FORMATION_PEERS cannot be used: it is set in this process's environment, so the launch is refused
  hint: MURMUR_FORMATION_PEERS is set by a formation member's runtime inside its own WASM components, never in a process environment; unset it to run this capsule

It also refuses a mur run whose MURMUR_FORMATION_LIFELINE does not name the member's end of its lifeline, before a session directory exists:

error[E-RUN-046]: MURMUR_FORMATION_LIFELINE does not carry this member's formation lifeline: descriptor 9 is a character device, and a lifeline is a pipe's read end; a member that cannot hear its lifeline would keep running after its formation ends, so the launch is refused
  hint: MURMUR_FORMATION_LIFELINE is set by `mur run --roster` for each member it launches, beside MURMUR_FORMATION_ID, and is not for operators; unset it to run this capsule on its own
The reason says Means
it is not a decimal descriptor number The value is not digits only. A blank value is absent, not refused
descriptor N is a standard stream The value is 0, 1 or 2
descriptor N is not open The process holds no descriptor N
descriptor N is a character device, a regular file, a directory, a socket Descriptor N is not a pipe
descriptor N is open for writing Descriptor N is a pipe's write end
it is set, but MURMUR_FORMATION_ID is not A lifeline belongs to a formation member
it was already read by this process The lifeline is read once per process
a script capsule opens no door Only an agent capsule can be a formation member

Unset the variable to run the capsule on its own.

E-RUN-047 — the capsule changed since admission

Every member mur run --roster starts is handed the sha256 of the artifact admission resolved for it. A member whose installed artifact now hashes differently — reinstalled or republished between admission and launch — refuses before staging, rather than run other bytes:

error[E-RUN-047]: capsule 'local/coder@1.2.0' resolved to bytes with sha256 7bec909458461f9f56fca830b2c939a63eb79f2041c758e7390c1a9704a0167f, but it was admitted with sha256 0000000000000000000000000000000000000000000000000000000000000000
  hint: the installed artifact changed after the roster was admitted; launch the formation again so admission reads what is installed now

Under mur run --roster it reaches the operator inside E-RUN-045, quoted from the member's stderr. Launching again admits what is installed now.

E-RUN-048 — the session's door is public

mur token named a running session whose capsule declares no network.authentication. Its door takes no token, so none was minted:

error[E-RUN-048]: ses_019f01a940ce7761854e768ecbe3d399 has a public door: its capsule declares no network.authentication, so it minted no token

Call the door without one, or check that the address names the session you meant: @1 is the most recent running session on the machine, whichever capsule it is.

E-RUN-049 — the session minted no such credential

mur token --credential named a credential the session's manifest does not declare. The message lists every name it did mint, operator first:

error[E-RUN-049]: ses_019f01a940ce7761854e768ecbe3d399 minted no credential 'admin'; it has: operator, reader, watcher

Pass one of the listed names. A credential added to network.authentication.credentials after launch is minted by the next launch, not this one.

E-RUN-050 — a link inside the workdir

The runtime met a symbolic link, or the wrong kind of file, inside the work folder, the records directory or the runtime directory, refused to write, read, create or remove through it, and wrote nothing outside them. mur run reports this code in two cases:

When Typical paths
Staging the session, before the capsule starts The tools, skills and MURMUR.md staging writes in the runtime directory
During the task mur run ran, after the capsule created a link The operator's task.md read when the session starts, a script capsule's out/result.txt, peer-in/

The message names the path and the part of it that is the link. A path in the work folder is named relative to it; a path in the runtime or records directory, by its absolute path:

error[E-RUN-050]: refused to create directory peer-in: peer-in is a symbolic link inside the workdir
  hint: the runtime never follows a link inside the workdir; replace peer-in with a regular file or directory, or remove it, and run again

mur trace rm raises it too, for a link or a file that is not a directory in place of the work folder or records directory it removes. Nothing is removed, and nothing the link points at is touched. The message names the directory the path is relative to:

error[E-RUN-050]: refused to remove ses_019f0193c7d871a5b2e30ff41a7c0ce1: ses_019f0193c7d871a5b2e30ff41a7c0ce1 is a symbolic link (in /home/me/proj/workdir)
  hint: the runtime never follows a link inside the workdir; replace /home/me/proj/workdir/ses_019f0193c7d871a5b2e30ff41a7c0ce1 with a regular file or directory, or remove it, and run again

The hint names where the path is:

Path Hint
In the workdir the runtime never follows a link inside the workdir; replace <path> with a regular file or directory, or remove it, and run again
Under ~/.murmur/records, mur's records directory — mur trace rm's session records directory, or one a run writes to <path> is in mur's records directory, where mur never follows a link; remove it, or replace it with the directory it stands in for, and run again
error[E-RUN-050]: refused to remove ses_019f0193c7d871a5b2e30ff41a7c0ce1: ses_019f0193c7d871a5b2e30ff41a7c0ce1 is a symbolic link (in /home/me/.murmur/records/home/me/proj/workdir)
  hint: /home/me/.murmur/records/home/me/proj/workdir/ses_019f0193c7d871a5b2e30ff41a7c0ce1 is in mur's records directory, where mur never follows a link; remove it, or replace it with the directory it stands in for, and run again

Remove the link, or replace it with the file or directory it stands in for. A refusal during a task also writes a workdir_path_refused trace line naming the path, and an A2A task that meets one ends failed with the refusal as its status message; see Links inside the work folder.

E-RUN-051 — a delegated child's workdir is not the one its parent created

A capsule that delegates creates the child's work folder under children/ in its own records directory and launches the child with that directory's identity. The child compares it with the --workdir it opens, before writing anything there, and refuses when they differ: something replaced the directory under children/, with a link or another directory, between its creation and the launch:

error[E-RUN-051]: the workdir /home/dev/.murmur/records/home/dev/project/ses_019f01a9c3e47d1a9b2c3d4e5f6a7b8c/children/worker-3f9c2a1b7e4d5c60 is not the directory the delegating capsule created (expected 2049:1311021, found 2049:1311042)
  hint: the delegating capsule's child work folder in children/ changed between the directory's creation and this launch; nothing was written

The delegation fails at the child's start, as any child that cannot launch does. Nothing was written under either directory.

E-RUN-052 — a driver choice has no output cap

Each driver choice of a transport: http capsule takes its output cap from inference.max_tokens, or else from the models: entry its driver declares for its model. With neither, the launch is refused before any request leaves:

error[E-RUN-052]: driver choice 'primary' has no output cap: driver murmur/driver-openai@1.0.0 declares no limits for model 'gpt-5.6-luna' and the manifest sets no inference.max_tokens
  hint: set inference.max_tokens to the model's output cap, or use a release of the driver whose murmur.yaml lists the model under models:

The choice is primary or an inference.alternates entry's name. The model id is matched by exact string equality, so a dated id and its alias are separate entries. Set inference.max_tokens in the capsule's manifest, or install a release of the driver that lists the model. mur doctor reports the same condition as a fix.

A driver whose own models: table is malformed refuses the launch under the same code, naming the driver and the field. An entry's max_input_tokens above its context_window is one such table:

error[E-RUN-052]: driver local/driver-glm@0.1.0 ships a malformed models: table: murmur.yaml: field 'models.glm-5.3.max_output_tokens' must be an integer from 1 to 4294967295
  hint: the driver's own murmur.yaml is at fault; use a release of the driver whose models: table is well formed

mur install and mur publish refuse such a payload with E-REG-011, so the launch meets one only in a store written some other way.

E-RUN-053 — the session reached its deadline

mur run --deadline <SECONDS> ends the session that many seconds after mur run started. Reached, the running task is cancelled — or, when lifecycle.keep_working is keeping it working, ends ok with its last turn end's answer — the session's teardown runs, and the run exits 1:

status:  deadline_reached
error[E-RUN-053]: the session reached its --deadline of 600 seconds; the runtime stopped the running task and ended the session
  hint: raise --deadline, or set lifecycle.deadline_notice_secs so the agent is told sooner; `mur trace show <session>` shows where the task was when the deadline fell

The trace records deadline_reached and ends with session_end.exit_status: "deadline_reached".

A session runs under the earliest deadline placed on it. When that is the one it inherited, from its formation's --deadline or from the capsule that delegated to it, the message names the moment instead:

error[E-RUN-053]: the session reached the deadline it inherited, at 2026-10-20T14:00:00Z, from the formation or the capsule that launched it; the runtime stopped the running task and ended the session
  hint: raise the --deadline the formation or the delegating capsule was launched with; `mur trace show <session>` shows where the task was when the deadline fell

A formation launched with mur run --roster --deadline exits 1 with this code when its entry member ends at the deadline. When the deadline falls before the entry member has started, the launcher stops every member it started and names them:

error[E-RUN-053]: the formation reached its --deadline of 60 seconds before its entry member started
  stopped: coder (pid 41871), reviewer (pid 41872)
  hint: raise --deadline so it covers the members' launch as well as the task; nothing the formation started is still running

The same code refuses a session staged with a deadline that its launch cannot enforce: one launched by a program embedding the runtime rather than by mur run, or a script capsule.

E-RUN-054 — the compaction cap is above the model's ceiling

inference.compaction.max_tokens caps the calls a hook makes while it serves on-compaction. The primary driver serves those calls, so a value above the max_output_tokens its models: table declares for the compaction model — inference.compaction.model, else inference.model — refuses the launch before any request leaves:

error[E-RUN-054]: inference.compaction.max_tokens (9000) is above the output ceiling of 8192 that driver local/murmur-driver-openai@0.1.0 declares for compaction model 'gpt-5'
  hint: lower inference.compaction.max_tokens to at most 8192, or remove it to use the driver's ceiling

A value equal to the ceiling is accepted. A compaction model the table does not list has no ceiling to check against, and the value is used as written. mur doctor reports the same condition as a fix.

E-RUN-055 — a driver choice has no input budget

Every inference call's input budget is the input ceiling, or the context window less the output cap the call sends, whichever is smaller. An output cap at or above the window leaves no input tokens, and the launch is refused before any request leaves:

error[E-RUN-055]: driver choice 'primary' has no input budget: its context window of 200000 (manifest) less its output cap of 200000 (manifest) leaves no input tokens; input ceiling none (model 'test-model', driver local/murmur-driver-anthropic@0.1.0)
  hint: lower inference.max_tokens below the context window, or raise context.max_tokens above the output cap
Field Value
Choice primary or an inference.alternates entry's name. Each choice is checked on its own window
Window, cap Each followed by its source: manifest or driver
Input ceiling The driver's max_input_tokens for the model, or none

The same code refuses an inference.compaction.max_tokens at or above the compaction model's window — context.max_tokens, else the context_window the primary driver's models: table declares for that model:

error[E-RUN-055]: compaction calls have no input budget: the context window of 100000 (manifest) for compaction model 'gpt-5.6' less inference.compaction.max_tokens of 100000 leaves no input tokens; input ceiling 922000 (driver local/driver-openai@0.1.0)
  hint: lower inference.compaction.max_tokens below 100000

A compaction model with neither window is not checked. mur doctor reports both conditions as a fix.

E-RUN-056 — a script capsule has no door

mur watch, mur cancel and mur control connect to a capsule's door. The address named a running script capsule, which binds none, so nothing is connected to:

error[E-RUN-056]: ses_019f01a940ce7761854e768ecbe3d399 is a script capsule: it binds no door, so there is nothing to connect to
  hint: mur ps lists it and mur stop ends it

mur stop ends a script capsule with a signal, and its trace records what it did.

E-RUN-057 — a computer tool on a model without vision

A computer tool returns screenshots, so every transport: http driver choice of a capsule that declares one — the primary and each alternate — must run a model that reads images. A choice whose driver's models: table marks its model vision: false refuses the launch before the session directory exists:

error[E-RUN-057]: tool 'computer' is a computer (provider_tool: computer) but model 'probe-model' of driver choice 'primary' has no vision: driver local/driver-example@1.0.0 declares vision: false for it
  hint: run that driver choice on a model that reads images, or remove the computer tool from artifacts: — see https://docs.murmur.nexus/reference/diagnostics/#e-run-057

The first such tool and the first such choice, primary first, are named. A model whose entry has no vision key, or that the table does not list, is not refused. A transport: process capsule is not checked. A pull-artifact of a computer tool into such a session is refused the same way.

E-RUN-058 — a malformed provider_tool: block

A stored tool artifact's provider_tool: block does not parse, or sits on an artifact whose runtime is driver, hook or skill:

error[E-RUN-058]: artifact 'local/computer@1.0.0' ships a malformed provider_tool: block: murmur.yaml: field 'provider_tool.display_width' must be an integer from 1 to 65535
  hint: provider_tool: holds kind: computer with display_width and display_height, each an integer from 1 to 65535; install a version of the artifact that ships a valid block, or remove it from artifacts: — see https://docs.murmur.nexus/reference/diagnostics/#e-run-058

mur build refuses the same block with E-BLD-006.

E-CAP-004 — staged runtime below the sealed floor

A staged_runtime grant is staged into a composed root, and a composed root is built only for a capsule that asked for sealed (see A weaker declaration is never silently upgraded). A capsule declaring staged_runtime below an effective sealed floor is therefore refused before any registry pull, artifact compile or workdir creation:

error[E-CAP-004]: capabilities.shell.staged_runtime is declared for python3 but the effective containment floor is 'scoped' — staging a runtime tree requires the 'sealed' floor, because there is no composed root to bind-mount it into below that
  hint: set `capabilities.containment: sealed` in murmur.yaml (or pass `--containment sealed`) so the capsule gets a composed root to stage the runtime into, or remove the capabilities.shell.staged_runtime grant.

The check reads the declared floor alone, so it fires identically on a host that could deliver sealed. That is what separates it from E-CAP-003, whose remedy points the opposite way:

E-CAP-003 E-CAP-004
What went wrong the host is too weak for what the capsule declared the capsule declared too little for what it asked for
Remedy lower the floor, or move hosts raise the floor, or drop the grant

mur doctor surfaces the same condition as a warning ahead of a run.

E-CAP-005 — no network namespace for the subprocess tree

capabilities.network.allow is enforced for a native subprocess by putting its whole tree in a network namespace of its own, whose only way out is an egress proxy in the runtime process. A Linux host where that namespace cannot be created refuses the launch:

error[E-CAP-005]: this host cannot give the capsule's subprocess tree its own network namespace, so capabilities.network.allow cannot be enforced for it: <failed call> failed with <ERRNO> (<description>). <likely cause and remedy> Host readings: kernel.apparmor_restrict_unprivileged_userns=<0|1>; AppArmor profile confining mur: <profile>.

mur decides by creating the namespace: a short-lived child runs exactly the construction a launch runs, and its result is the answer. AppArmor's unprivileged-userns restriction (kernel.apparmor_restrict_unprivileged_userns) and the profile confining mur are read only to explain a refusal. A container confined by its own AppArmor profile, on a host whose restriction reads 1, is not refused when the namespace can be created inside it.

The refusal names the failed call, its errno and the host readings. Together they identify the host condition:

Failed call and errno Readings line Host condition What to do
writing uid_map/gid_map or unshare(CLONE_NEWUSER \| CLONE_NEWNET), EPERM =1; profile unconfined or not reported AppArmor's restriction applies to mur, and no mur-sealed profile confines it. Ubuntu lets the unshare through and refuses the identity map that follows, so the failed call is usually writing uid_map/gid_map Install and load the AppArmor profile shipped with mur; for a checkout build, run scripts/install-dev-apparmor.sh. The refusal gives both commands. Setting the restriction to 0 host-wide is the last resort
Any call, usually unshare(…) with EPERM Profile is another name, such as docker-default A container confined by its own AppArmor profile; that profile, the container runtime's seccomp filter and its capabilities refused the namespace. The restriction setting does not govern it Run the container with an AppArmor profile and seccomp policy that permit unshare(CLONE_NEWUSER), or with --cap-add SYS_ADMIN, or create the network namespace outside the container and run mur inside it
Any call, usually unshare(…) with EPERM =0, or AppArmor not enabled A container runtime's seccomp filter or a missing CAP_SYS_ADMIN, or another security module such as SELinux The container remedies in the row above; on a bare host, the policy of the security module in use
Any call Profile mur-sealed The mur-sealed profile confines mur and the namespace still failed: the loaded profile may be out of date sudo apparmor_parser -r /etc/apparmor.d/mur-sealed, then compare the installed profile in mur doctor
writing uid_map/gid_map, bringing lo up or installing the local default route Any other The namespace was created and then could not be owned (uid_map) or configured (lo, route) — a confinement that withholds capabilities inside the namespace As the row matching the readings line
unshare(…), ENOSPC, EINVAL, ENOSYS or EUSERS Any The kernel does not provide the mechanism: user.max_user_namespaces is 0 or exhausted (ENOSPC), the kernel is built with CONFIG_USER_NS=n (EINVAL, ENOSYS), or the nesting limit for user namespaces is reached (EUSERS) Raise user.max_user_namespaces, run on a kernel built with CONFIG_USER_NS=y, or run mur fewer user namespaces deep
No call: the runtime's own message, such as fork() for the namespace creator failed or the namespace creator exited without handing anything over (killed by signal …) Any The namespace creator could not run, or stopped before reporting: a process or descriptor limit or memory ran out, or a policy killed it Re-run once the limit is relieved; if the message names a signal, allow the creator's syscalls in the policy that killed it

This applies to every Linux capsule that can spawn a subprocess, including one whose capabilities.network.allow is empty: with the namespace missing, an empty allowlist would mean unrestricted egress rather than none. There is no path that continues at reduced enforcement.

A capsule can spawn a subprocess through any of the routes below, and mur run checks each at a different point:

Route Checked
capabilities.shell.allow or capabilities.spawn.allow is non-empty At staging, before any artifact is pulled
capabilities.install.tool is non-empty: a pattern can match a native tool a pull installs At staging, before any artifact is pulled
A declared runtime: tool artifact ships a native implementation At launch, once the artifacts are staged and before any component is instantiated
A native tool pulled into the session by manage.pull or pull-artifact At that tool's first call, which fails with E-CAP-005 and the same reason

Every check point and mur doctor print the same reason. mur doctor reports the answer as the network namespace: line of its AppArmor / user namespaces block, for every project, beside the mount namespace: and sealed root: lines. How the three lines and the readings tell host conditions apart: Telling host conditions apart.

It is not part of the containment ladder — every class needs the namespace equally — so neither raising nor lowering a declared floor changes this answer.

When the capsule declares a provider tool, the refusal names each such tool on its own line under the message, before the hint: required by: tool 'computer' (provider_tool: computer).

E-CAP-006 — interpreted entrypoint unreachable under sealed

Staging a shell.allow binary's own ELF dependency closure into the composed root is what makes that binary runnable, and for an interpreted entrypoint it stages nothing useful. A console script such as pip at ~/.local/bin/pip is a #! script, not an ELF image, so its dependency closure is empty — and the package it needs (~/.local/lib/python3.12/site-packages/pip) is a different directory nothing derives. The capsule would launch cleanly and fail deep into a run, so under a declared sealed floor this is decided at staging, before any registry pull, artifact compile or workdir creation.

mur run refuses unless one of the following holds: the script already resolves under a fixed sealed runtime path (/usr, /bin, /sbin, /lib, /lib32, /lib64, /libx32), or it lives inside a directory a staged_runtime/interpreter_runtime grant already names, or some such grant names the script or its shebang interpreter:

error[E-CAP-006]: capabilities.shell.allow grants 'pip' (/home/dev/.local/bin/pip, a script run by 'python3') under the 'sealed' containment floor, but nothing declared makes the interpreted entrypoint's own package tree reachable inside the composed root — ...
  hint: if this command also has a module form, prefer it: allowlist the interpreter and invoke the module through it — `python3 -m pytest` in place of `pytest` — which runs the same code and needs no grant at all when the interpreter is the distro's ...

Two remedies answer this refusal, and which one you want depends on the command:

Situation Remedy
The command has a module form — python3 -m pytest runs what pytest runs, and node, ruby and perl have the same form Allowlist the interpreter and invoke the module through it. Under a distro interpreter this needs no grant at all, because /usr/bin is already a fixed sealed runtime path, and it stays correct across base images. murmur does not check whether a module form exists, so confirm one from the command's own documentation
The command has no module form — a wrapper script with no module entry point, or a case where that exact script must run Declare capabilities.shell.interpreter_runtime (or staged_runtime) for the interpreter, naming the directories its import machinery reads, measured with strace -f -e trace=openat,getdents64 <the command>. Those directories are host-specific — /opt/venv/lib/python3.11/site-packages on one image, a distro dist-packages on the next — so declaring them ties the capsule to one image's layout

The name match is deliberately loose, and the guarantee is correspondingly narrow: declaring interpreter_runtime for python3 satisfies every python3 script, whatever directories the grant names. murmur does not derive an interpreted program's import closure — sys.path, .pth files and whatever the script does at runtime make that undecidable in general — so this check verifies you declared something, never that the directory you declared is the right one. Measuring it is still yours to do, which is why the hint names the strace invocation.

The check reads only the declared floor, never a host probe, and is inert at scoped and advisory: below sealed there is no composed root, and the host filesystem is simply the host filesystem. mur doctor surfaces the same condition ahead of a run, as a warning.

Two neighbouring codes are easy to confuse with this one:

Code Fires when Say
E-CAP-004 a grant exists at too low a floor raise the floor
E-CAP-006 the floor is already sealed and no grant exists add a grant
W-SEC-012 an allowlisted compiler driver's helpers have no Execute grant name the helper's directory in a grant

E-CAP-007 — export root outside the workdir

exports.files.root and exports.peer_files.root each name a subtree of the work folder. When one of those paths already exists and resolves somewhere else — because it is a symlink pointing out of the workdir — mur run refuses at staging, before the workdir is created and before any session runs:

error[E-CAP-007]: exports.files.root 'out/' resolves to '/srv/elsewhere', which is outside the capsule workdir '/home/dev/project'
  hint: point the export root at a directory inside the capsule workdir. A root that already exists as a symlink out of the workdir is refused whole rather than followed — see https://docs.murmur.nexus/reference/resource-plane/

The root is refused whole rather than followed one file at a time: a per-request check would let the first file leave before anyone noticed. A root that does not exist yet is accepted — the agent may create it during a task.

This is not a containment shortfall, and no change to capabilities.containment affects it. An export is a disclosure the operator makes, and the path named simply is not inside the capsule. See Resource plane and Containment and disclosure.

E-CAP-008 — a persistent capsule needs a short handle lifetime

A peer-file handle is bounded by the minting capsule's own lifetime: the key it is verified with is generated in memory at launch and destroyed at teardown, so an ephemeral capsule (lifecycle.after_task: exit, the default) needs no declared ceiling at all.

lifecycle.after_task: sleep withdraws that bound deliberately. The capsule stays alive, its instance key stays alive, and a handle sitting in persisted A2A message history stays redeemable — so the declared lifetime becomes the only one there is. It must therefore be declared, and it must be at most 15m:

error[E-CAP-008]: exports.peer_files with lifecycle.after_task: sleep requires exports.peer_files.max_ttl to be declared and at most 900s (declared 1800s); a handle's lifetime is not a durability mechanism
  hint: declare `exports.peer_files.max_ttl: 15m` or shorter, or drop `lifecycle.after_task: sleep` so teardown bounds every handle instead. A consumer that needs these bytes after the capsule is gone should have the operator relaunch the runtime against the still-present workdir and request again — see https://docs.murmur.nexus/reference/resource-plane/

mur run refuses at staging, before the workdir is created and before any session runs, so no trace.jsonl appears.

A handle's lifetime is not a durability mechanism, and the remedy is never a longer one. Workdirs persist past teardown: a consumer that needs the bytes after the capsule is gone should have the operator relaunch the runtime against the same workdir and request again — see The minting key.

E-CAP-009 — capabilities.state.store does not name a usable store

A durable state store is one directory under ~/.murmur/state/, so its name is one path segment: non-empty, no /, not . or .., not beginning with ., and not absolute. Anything else names either a directory tree or somewhere outside the state root, and neither is a store:

error[E-CAP-009]: invalid state store name '../escape': a store name is a single path segment and must not contain '/'
  hint: capabilities.state.store names one directory under ~/.murmur/state/, so it must be a single path segment: no '/', no '.' or '..', not absolute, and not starting with a dot. Omit `store:` to use the capsule name — see https://docs.murmur.nexus/reference/workdir/#state-store

The same code covers a well-formed name whose directory this host cannot supply — an unresolvable home directory, or a ~/.murmur/state/ that cannot be created as a 0700 directory:

error[E-CAP-009]: state store 'shey' is unavailable at /home/dev/.murmur/state: failed to create the directory: File exists (os error 17)
  hint: a capsule declaring capabilities.state needs a resolvable home directory: run with HOME set to an absolute path, and make sure ~/.murmur/state/ can be created as a 0700 directory — see https://docs.murmur.nexus/reference/workdir/#state-store

Both are decided at staging, before any registry pull, workdir creation or component instantiation, so nothing is created under ~/.murmur/state/ and no trace.jsonl appears. mur run --explain-scope refuses the same declarations with the same code, so a manifest that would not launch does not pass the diagnostic either.

Neither is a containment shortfall, and no floor change fixes one: the remedy is the store name or the host's home directory, never capabilities.containment.

E-CAP-010 — an artifact's config: block cannot be delivered

config: on an artifact entry travels to that artifact as one environment variable holding JSON, so the block has to be a string-keyed mapping that serializes to JSON within 65536 bytes. Each rule refuses by name, quoting what the entry declared:

error[E-CAP-010]: invalid config for artifact 'murmur/tool-corpus': 'config:' must be a mapping of keys to values, but this entry declares a sequence
  hint: config: on an artifact entry must be a mapping with string keys that serializes to at most 65536 bytes of JSON; it is delivered to that artifact alone as MURMUR_ARTIFACT_CONFIG. Omit the key entirely to deliver no variable, and keep secrets out of it — see https://docs.murmur.nexus/reference/manifest/#artifact-config

An oversized block is refused rather than truncated, and the message names the size it serialized to alongside the limit:

error[E-CAP-010]: invalid config for artifact 'murmur/tool-corpus': 'config:' serializes to 70011 bytes of JSON, over the 65536-byte limit for MURMUR_ARTIFACT_CONFIG

config: written with no value under it is an empty block, refused on the same terms. Omit the key to deliver no variable at all.

The refusal is decided at staging, before any registry pull, workdir creation or component instantiation, so no work folder or records directory appears and no trace.jsonl is written. mur run --explain-scope refuses the same blocks with the same code.

The runtime checks the shape and not the meaning: which keys a given artifact requires is that artifact's own business, and a missing one surfaces as that artifact's error rather than this one.

E-CAP-011 — a conversation record path segment is not usable

The conversation record lives at ~/.murmur/conversations/<record>/<context-id>/, and both segments come from the operator: context.record_store and mur run --context. Each must be a single path segment, and a value that is not refuses the launch, quoting what was written:

error[E-CAP-011]: invalid context.record_store 'a/b': must be a single path segment: no '/', no '.' or '..', not absolute, and not starting with a dot
  hint: context.record_store names one directory under ~/.murmur/conversations/, and --context names one directory beneath that, so each must be a single path segment. Omit context.record_store to use the capsule name, and omit --context to get a fresh id per task — see https://docs.murmur.nexus/reference/manifest/#field-context

The refusal is decided at staging, before any registry pull, workdir creation or component instantiation, so nothing is created under ~/.murmur/conversations/.

Distinct from E-CAP-009, which is the same shape rule applied to capabilities.state.store and points at a different key and a different directory.

A contextId an A2A client sends is not an operator value and never refuses a launch: a task whose context id is not a usable segment simply goes unrecorded, reported once to stderr and to logs/bootstrap.log.

E-CAP-012 — invalid filesystem.read_only entry

A capabilities.filesystem.read_only entry is not a usable subdirectory of the work folder:

error[E-CAP-012]: invalid read-only path '/etc': read-only path must be relative to the workdir
error[E-CAP-012]: invalid read-only path '../outside': read-only path cannot escape the workdir via '..'

The same two rules E-CAP-002 applies to filesystem.scope, applied to each entry of the read-only list. The check runs at staging, before any registry pull, workdir creation or component instantiation, so no session directory is created and no call is ever checked against a rule the runtime could not build. An empty or whitespace-only entry is dropped at manifest parse rather than refused. See Read-only paths.

E-CAP-013 — an artifact claims a runtime-provided tool name

share-file, fetch-peer-file, delegate-task, submit-plan, switch-driver, call-member, end-without-answer, pull-artifact, remove-artifact, session-status and background-command are answered by the runtime itself, so no artifact may have one of them as its tool name, the part of its full name after the /. ana/pull-artifact is refused as pull-artifact would be:

error[E-CAP-013]: artifact 'ana/pull-artifact' has the tool name 'pull-artifact', which collides with a tool the runtime provides itself; the reserved names are share-file, fetch-peer-file, delegate-task, submit-plan, switch-driver, call-member, end-without-answer, pull-artifact, remove-artifact, session-status, background-command
  hint: the runtime answers these names itself, so an artifact under one of them would be shadowed at dispatch whatever the tool allowlist said. Rename the artifact, or drop the dependency if the runtime-provided tool is what you wanted — see https://docs.murmur.nexus/reference/runtime-provided-tools/

The check runs at staging, ahead of every artifact in the manifest, so no artifact is resolved, pulled or hash-verified and no session directory is created. mur run --explain-scope refuses the same manifest by the same code. An in-session pull of one of these names, by manage.pull() or by pull-artifact, is refused the same way, and the capsule receives that refusal as an error string.

Shell binary names are not reserved: they come from capabilities.shell.allow. See Runtime-provided tools.

E-CAP-014 — a variable nothing sets

A variable this project needs is set by neither this shell nor the workspace .env. Two manifest keys name one:

Key Read from
capabilities.env.allow Any capsule in the capabilities.spawn.allow closure mur doctor walked
artifacts[].gateway.api_key The project manifest, where the value is written ${VAR}
error[E-CAP-014]: this project needs 1 variable nothing in this environment sets: WORKER_TOKEN
  Every name is copied from the launching shell at the moment of the spawn, so an unset one reaches the capsule that declared it as absent — export it, or declare it in the workspace .env.

Reported only by mur doctor. A capsule declaring no capabilities.spawn.allow has no closure to walk and is reported only for a variable its own manifest references. What mur run does with the same name depends on the key: an unset capabilities.env.allow name is omitted from the launched environment, and an gateway.api_key reference it cannot resolve is refused with E-MAN-003.

Names only: no value is read into the report or printed. A name set to the empty string counts as set, because the runtime copies it through as-is. The matching stdout line is ✗ NAME unset — <capsule>@<version>, naming every capsule that needs the name. A name reached through a field other than capabilities.env.allow carries that field in brackets, as ✗ NAME unset — solo@0.0.1 (artifacts.murmur/driver-anthropic.gateway.api_key). This is one of the two Environment requirements findings that make mur doctor exit non-zero.

E-CAP-015 — a declaration mur-roost will refuse

A capsule declares a capabilities.env.allow entry the capsule that spawns it does not hold:

error[E-CAP-015]: 1 declaration in the spawn closure exceeds the envelope the capsule spawning it holds, and mur-roost refuses a spawn that widens one.
  A child may declare no more than its parent does on capabilities.env.allow.

A spawn may narrow its parent's envelope, never widen it, so mur-roost refuses this delegation at the moment the parent reaches for it. mur doctor predicts that refusal offline, naming both capsules and the axis:

  declarations mur-roost will refuse:
    ✗  worker@0.1.0 declares capabilities.env.allow 'WORKER_ONLY', which its parent root-capsule@0.0.1 does not

Add the name to the parent's capabilities.env.allow, or drop it from the child's.

E-CAP-016 — an env.allow entry the credential backstop strips

A capabilities.env.allow entry names a variable the credential backstop removes from every WASM component's environment:

error[E-CAP-016]: capabilities.env.allow names 'GITHUB_TOKEN' (credential backstop pattern 'GITHUB_TOKEN'), 'MY_SERVICE_SECRET' (capabilities.shell.strip_env pattern '*_SERVICE_SECRET'); these entries are removed from every guest environment before any guest is built, so the grant would deliver nothing
  hint: remove these entries from capabilities.env.allow. No manifest setting exempts a name from the credential backstop, and a provider key never belongs in env.allow: the runtime presents it itself through the artifact's gateway.api_key — see https://docs.murmur.nexus/reference/diagnostics/#e-cap-016

Fires when: a name matches one of these patterns. The error names every matching entry once, in declaration order, with the pattern that matched and where that pattern comes from.

Pattern source Patterns
credential backstop The built-in list: GITHUB_TOKEN, AWS_*, DOCKER_*, *_API_KEY among them
capabilities.shell.strip_env The patterns the manifest declares

A built-in pattern covers names that carry no secret too, such as AWS_REGION and DOCKER_HOST.

The verdict comes from the name alone, so a host that has the variable set and one that does not get the same result, and no value ever appears in the message. It is reported by:

Surface Outcome
mur run, mur new, mur eval, and every capsule mur-roost launches Refused at staging, before any artifact is resolved or pulled and before a session directory is created
mur run --explain-scope Refused, by the same code
mur doctor warning[E-CAP-016], followed by `mur run` will refuse this capsule; the rest of the checklist still runs

Why it is a refusal: the backstop removes these names whatever the manifest declares, so the grant would deliver nothing, and a capsule that launched with it would look granted and hold nothing.

What to do: remove the entries. For a provider or third-party API key, set gateway.api_key on the entry of the artifact that uses it: the runtime presents the key itself. For a variable a component needs, give it a host name no pattern matches. A name the backstop keeps but that looks like a credential is reported by W-SEC-024.


E-CAP-017 — a gateway on a native tool

A runtime: tool entry declares gateway:, and the artifact it names ships a native implementation.

error[E-CAP-017]: artifact 'my-native-tool' declares 'gateway:' but ships a native implementation, whose requests never pass the credential gateway
  hint: a native tool's requests leave through the egress proxy and never pass the credential gateway, so no key could be attached; remove gateway: from its entry, or use a WASM build of the tool

mur run refuses at staging, before a session directory is created. A native tool runs as a host subprocess whose requests the runtime cannot attach a key to.

E-CAP-018 — a gateway that binds no credential

An artifact entry's gateway: names an endpoint but has no non-blank api_key and does not write keyless: true. An api_key that is absent, "", whitespace or null counts as none, as does keyless: false.

error[E-CAP-018]: murmur.yaml: invalid artifact declaration at index 0: artifact 'local/tool-web-search' declares gateway.endpoint 'https://api.tavily.com' but binds no credential
  hint: set gateway.api_key: ${NAME} on this entry to have the runtime present a key, or write gateway.keyless: true if this upstream takes no key — the runtime never infers keylessness from the address

Refused when the manifest is parsed, by mur run, mur doctor and a roost reading a child's manifest alike, before a session directory is created or a request is sent. The endpoint's address plays no part: a loopback endpoint is refused the same way.

Remedy When
api_key: ${NAME} on the entry, with the key stored by mur config set -g credentials.NAME <key> The upstream takes a key
keyless: true on the entry The upstream takes no key, such as a local model server — see Keyless upstreams

E-CAP-019 — a process driver needs an undeclared variable

A process driver requires variables in its CLI's environment, and capabilities.env.allow does not declare every one of them. The CLI sees only the variables the manifest declares. mur run refuses at staging and names each missing variable.

error[E-CAP-019]: process driver 'my-process-driver@1.0.0' requires MY_PROFILE in its environment, which capabilities.env.allow does not declare
  hint: add each variable to capabilities.env.allow — the harness sees only variables the manifest declares

E-CAP-020 — two artifacts share a tool name

A session offers each declared artifact to the model by its tool name, the part of its full name after the /, and stages it under tools/<tool name>/ in the session's runtime directory. Two declared artifacts with one tool name are refused, whatever their scopes:

error[E-CAP-020]: artifacts 'murmur/tool-edit' and 'ana/tool-edit' share the tool name 'tool-edit'; a capsule offers each artifact to the model by its tool name, so no two may share one
  hint: declare one of them: the model calls each artifact by the part of its name after the '/', so two scopes cannot both serve one tool name in a capsule — see https://docs.murmur.nexus/reference/diagnostics/#e-cap-020

A source: entry counts by its bare name, so - name: tool-edit with source: beside murmur/tool-edit is refused the same way. The check runs before any artifact is resolved, so nothing is resolved and no session directory is created. Declare one of the two.

Conversation record errors

mur conversation acts on the record store directly. All three refusals happen before anything is read or written.

E-CNV-001 — no such record or context

show, rm and truncate name one context id, optionally narrowed with --record. Nothing matching it is refused naming what was looked for and where. For show, a context directory that holds neither conversation.jsonl nor harness-session.json matches nothing:

error[E-CNV-001]: no context 'ctx_nowhere' in any record under ~/.murmur/conversations/
  hint: `mur conversation ls` lists every record and context on this host

E-CNV-002 — a context id is ambiguous across record stores

A context id is unique inside one record store and nowhere else: two capsules can be handed the same one, and two capsules can be pointed at one context.record_store. An id present under more than one store is refused naming every store it is in, rather than guessing which conversation to show, delete or rewrite:

error[E-CNV-002]: context 'ctx_fixed' is present under 2 record stores: store-a, store-b
  hint: pass --record <NAME> to say which one, e.g. --record store-a

E-CNV-003 — invalid --keep

mur conversation truncate --keep 0 is refused, because truncating a record to nothing is mur conversation rm:

error[E-CNV-003]: --keep must be at least 1
  hint: truncating a record to nothing is `mur conversation rm ctx_fixed`

Build lints

mur build checks the file set it is about to pack before it writes a byte of zip. What it finds is reported two ways:

  • E-BLD-NNN errors stop the build. No .mur.zip is written — not a partial one, not an empty one.
  • W-BLD-NNN warnings go to stderr and the build completes. They flag a packaging mistake that still produces a working artifact.

Both classes are about packaging: what ends up inside the archive, and whether the runtime will be able to launch it. They are distinct from the W-SEC-NNN warnings, which are about capability and enforcement posture.

E-BLD-001 — invalid artifact name

error[E-BLD-001]: invalid artifact name '<name>': <reason>

An artifact name: is its bare name. It becomes a filename (<name>-<version>.mur.zip), the tool name the model calls and a directory in the local store, so it is held to the lowest common denominator of all three:

  • non-empty, at most 100 characters
  • ASCII lowercase letters, digits and - only
  • no leading or trailing -

Uppercase letters, spaces, \ and _ are rejected by the character rule. A / is refused on its own, in name: and in mur build --skill <name> alike, because the scope belongs to the source the artifact is installed from — see Artifact names:

error[E-BLD-001]: invalid artifact name 'ana/tool-x': an artifact's own name carries no scope, so it may not contain '/'
  hint: the scope belongs to the source the artifact is published from, not to the artifact: name it 'tool-x', and it installs as <owner>/tool-x from github.com/<owner> or as local/tool-x from its .mur.zip file
name: my-tool        # ok
name: My Tool        # E-BLD-001
name: my_tool        # E-BLD-001
name: -my-tool       # E-BLD-001
name: ana/my-tool    # E-BLD-001

E-BLD-002 — unsafe or colliding packed entry

error[E-BLD-002]: requires_files entry '<entry>' has an unsafe path: <reason>
error[E-BLD-002]: requires_files entry '<entry>' is a symlink (<path>); declare the file it points to instead
error[E-BLD-002]: requires_files entries '<a>' and '<b>' both pack as the archive entry '<name>'
error[E-BLD-002]: requires_files entry 'murmur-build.json' packs as the archive entry 'murmur-build.json', which is reserved for mur build
error[E-BLD-002]: <path> is a symlink; a skill folder packs regular files only
error[E-BLD-002]: <folder> holds both SKILL.md and skill.md; a skill has one skill.md
error[E-BLD-002]: <folder>/murmur-build.json packs as the archive entry 'murmur-build.json', which is reserved for mur build
error[E-BLD-002]: skill source refused: <one of the three skill-folder messages above>

Every requires_files: entry must be a plain relative path to a real file inside the source directory, and the entries must not collide once they are inside the archive:

  • No absolute paths. /etc/hosts joined onto the source directory is /etc/hosts.
  • No .. components. This is the same rule a .mur.zip reader applies to an entry name it unpacks, applied at authoring time.
  • No symlinks. The link is not followed: packing it would ship whatever it resolves to — plausibly from outside the source tree — under the declared name.
  • No two declarations claiming one archive entry. The archive name is a rewrite of the declared path (\ → /), so two distinct files can otherwise land in the same slot, where one silently overwrites the other on unpack.

  • No murmur-build.json at the archive root. mur build writes that entry itself, with the artifact's built-at WIT level. The same name in a mur build --skill input is refused too.

A skill folder built with no murmur.yaml (see mur build skill packaging), or installed as a local source: directory, follows the same rules over every file it holds: no symlink anywhere, one skill.md at the root in whichever casing, and no root murmur-build.json. The skill source refused: form is mur run refusing a local source: directory before any workdir is created.

A requires_files: entry that redundantly names murmur.yaml is not a collision — it is dropped before packing, and reported as W-BLD-001 instead.

E-BLD-003 — packed entry set is not a launchable payload

error[E-BLD-003]: missing root .wasm file (expected capsule.wasm or one root *.wasm)
error[E-BLD-003]: multiple root .wasm files found: <names>

The artifact's runtime:/execution: resolve to a wasm artifact, but the entry set about to be packed does not contain exactly one payload the runtime could select. mur build never compiles anything, so a .wasm must already exist on disk and be declared in requires_files:.

The rule and the message are the runtime's own, so a build that passes this check cannot fail payload selection at mur run time:

  • exactly one root *.wasm entry → selected
  • a root capsule.wasm → always selected, however many other root *.wasm entries exist (which is why that case is a warning, not an error — see W-BLD-002)
  • zero root *.wasm entries → missing root .wasm file
  • two or more, none named capsule.wasm → multiple root .wasm files found

A *.wasm in a subdirectory is not a root entry and does not count. This check applies only to wasm artifacts — native (implementation: native) and static (runtime: skill, execution: static) artifacts are packed without it.

E-BLD-004 — manifest declares a retired block name

error[E-BLD-004]: murmur.yaml: field 'inference_auth' was renamed to 'upstream_auth'; this murmur reads only the new name
  hint: rename the block to upstream_auth: — an artifact published with inference_auth: is refused at launch with E-RUN-025

The artifact's murmur.yaml declares inference_auth:, the former name of the upstream_auth: block. mur build refuses before writing the .mur.zip, including when upstream_auth: is also declared. Rename the block; its fields are unchanged.

E-BLD-005 — malformed models: table

error[E-BLD-005]: murmur.yaml: field 'models.glm-5.3.max_output_tokens' must be an integer from 1 to 4294967295
  hint: models: is a driver's table of model id to max_output_tokens and context_window, and optionally max_input_tokens (at most context_window), each an integer from 1 to 4294967295, and vision, a boolean; a driver shipped with a malformed table is refused at launch with E-RUN-052

mur build refuses before writing the .mur.zip, naming the field:

models: Field named
Not a mapping models
An empty model id models
Declared on an artifact whose runtime is not driver models
An entry that is not a mapping models.<id>
An entry missing max_output_tokens or context_window models.<id>.<key>
A value that is not an integer from 1 to 4294967295 models.<id>.<key>
A max_input_tokens above the entry's context_window models.<id>.max_input_tokens
A vision that is not a boolean models.<id>.vision
An entry holding any other key models.<id>.<key>

The block's shape is in models: block.

E-BLD-006 — malformed provider_tool: block

error[E-BLD-006]: murmur.yaml: field 'provider_tool.display_width' must be an integer from 1 to 65535
  hint: provider_tool: goes on a runtime: tool artifact and holds kind: computer with display_width and display_height, each an integer from 1 to 65535; a tool shipped with a malformed block is refused at launch with E-RUN-058

mur build refuses before writing the .mur.zip, naming the field:

provider_tool: Field named
Declared on an artifact whose runtime is driver, hook or skill provider_tool
Not a mapping provider_tool
No kind, a kind that is not a string, or a kind other than computer (the message lists the known kinds) provider_tool.kind
A display_width or display_height missing, not an integer, or outside 1 to 65535 provider_tool.<key>
Any other key provider_tool.<key>

The block's shape is in provider_tool: block.

W-BLD-001 — declaration names a reserved archive entry

warning[W-BLD-001]: requires_files entry '<entry>' names the reserved archive entry '<name>', which mur build already packs

Two root entries have a fixed meaning inside a .mur.zip: murmur.yaml is seeded by the packer itself, and capsule.wasm is the payload the runtime prefers over every other root *.wasm. Declaring murmur.yaml in requires_files: packs nothing extra — the entry is deduplicated away and the artifact is byte-identical without it.

Remove the declaration.

W-BLD-002 — capsule.wasm shadows another root payload

warning[W-BLD-002]: root 'capsule.wasm' is always selected as the payload, so <names> ships but never runs

The artifact carries a root capsule.wasm and at least one other root *.wasm. The build succeeds — capsule.wasm makes payload selection unambiguous — but the other file is shipped and never executed.

Keep exactly one root *.wasm: drop capsule.wasm, or rename the payload you meant to run.

W-BLD-003 — compiled artifact packages build inputs

warning[W-BLD-003]: compiled artifact packages build inputs: <names>

A wasm or native artifact declares an obvious build input in requires_files: — Cargo.toml, Cargo.lock, a *.rs file, or anything under a target/ path. A compiled artifact ships its payload, not the sources it was built from, so this is almost always a stray declaration.

Static artifacts (runtime: skill) are exempt: their files are their content.


Deploy warnings

mur deploy run prints these on stderr after it has asked the target to compile the uploaded artifacts. The deploy continues, the capsule starts, and mur deploy run exits 0. See Precompile on the target.

W-DEPLOY-001 — the target did not precompile

Two forms:

warning[W-DEPLOY-001]: the target did not precompile (error: unrecognized subcommand 'precompile'); the capsule compiles its artifacts on its first launch
warning[W-DEPLOY-001]: the target could not precompile tool-echo@1.0.0; the capsule compiles it on its first launch
Form Cause Fix
did not precompile (<error>) The target's mur printed no mur precompile report. The parenthesis quotes the first line it printed on stderr: unrecognized subcommand 'precompile' means the target runs a mur release older than the command; an SSH error means the command never ran Set deploy.mur_version in the manifest to a release that has mur precompile, or pass --mur-binary
could not precompile <name@version>, … The report names those artifacts failed: the payload is not a .mur.zip, has no murmur.yaml that parses, or its WASM does not compile on the target Rebuild or re-publish the artifact. An artifact whose WASM does not compile also fails the capsule's launch

The capsule's first launch compiles what the target did not, taking as long as a launch with --no-precompile.

W-DEPLOY-002 — the target's decompression ceiling differs

warning[W-DEPLOY-002]: the artifact decompression ceiling is 524288000 bytes on this machine and 600000000 bytes on the target; the capsule runs, warm, under the target's value. To align them, pass --env MURMUR_MAX_ARTIFACT_DECOMPRESSED_BYTES=524288000

The target's MURMUR_MAX_ARTIFACT_DECOMPRESSED_BYTES — the value in the deploy's .env, or the default — is not this machine's. The target compiled and the capsule runs under the target's value, so the first launch is still warm, but an artifact that extracts here may be refused there, or the reverse. To run under this machine's value, pass the --env the warning gives.


Config errors

E-CFG-003 — a config file's registry: section cannot be used

mur install and mur deploy build each scope's source from the registry: section of every config file they read, and refuse a section they cannot use, naming the file. Nothing is requested or written. Other commands do not read registry:.

error[E-CFG-003]: /home/ana/.murmur/config.yaml sets registry.default, which this mur no longer reads: each scope has exactly one source, so there is no first source to choose
  hint: delete registry.default; a source is one registry.sources entry, `- repo: <owner>/<repo>` with an optional `token:`; it serves the scope named by its owner, and the murmur scope is always served by murmur-nexus/default-artifacts
Message names Fix
sets registry.default Delete the key
registry.sources[N] sets name:, type: or url: Delete those keys and keep repo: and token:
registry.sources[N] names no repo Add repo: <owner>/<repo>
repo '…' is not a GitHub repository written <owner>/<repo> Write the repository as <owner>/<repo>
repo '…' claims the reserved scope 'local' or 'murmur' Remove the entry. Only murmur-nexus/default-artifacts serves murmur, and an entry naming it may only set its token:
repo '…' names no usable scope The owner is not a valid scope; name another repository
registry.sources[N] (…) and registry.sources[M] (…) both serve scope '…' Keep one of them

Index errors

mur index writes an artifact index and mur search reads one. A refused mur index writes nothing and leaves an existing output file as it was.

E-NEW-006 — mur new could not read the artifact index

mur new writes each default artifact at the newest version that fits this mur in a published artifact index — see mur new. With no index to read it refuses and writes nothing. The message names the index and why it was not read:

error[E-NEW-006]: could not read the artifact index of github:murmur-nexus/default-artifacts: request failed: io: Connection refused
  hint: pass --index <PATH|URL> to take versions from an artifact index file or http(s) URL instead
Index Message names
Omitted --index: the latest default-artifacts release the artifact index of github:murmur-nexus/default-artifacts and what the source answered. A rate limit adds the token advice mur install gives to the hint
An --index URL the artifact index at <URL> and the request's failure or HTTP status
An --index file the artifact index file <PATH> and the read error

Restore the network or the source, or pass --index naming a copy of an index you hold.

E-NEW-007 — no fitting version of a default artifact

The artifact index mur new read is of another scope, does not list a default artifact it writes, or lists only versions that do not fit this mur. The message names the artifact and the index, and for versions that do not fit, the newest one and why:

error[E-NEW-007]: the artifact index github:murmur-nexus/default-artifacts v1.0.0 lists no version of murmur/driver-anthropic that fits this mur; the newest it lists, 1.1.0, needs murmur:driver/process@9.9.9, this mur serves murmur:driver@0.4.0
  hint: upgrade mur, or pass --index <PATH|URL> naming an artifact index that lists a version of murmur/driver-anthropic this mur fits

A version fits when this mur serves its WIT level and one of its payloads runs on this platform — see WIT levels.

E-IDX-001 — a file cannot be indexed

mur index refuses the whole directory when one .mur.zip in it cannot be indexed, naming the file and the reason:

error[E-IDX-001]: release/tool-editor-0.7.0.mur.zip cannot be indexed: its file name does not match its packed name 'tool-editor' and version '0.6.0': expected tool-editor-0.6.0.mur.zip, or tool-editor-0.6.0-<platform>.mur.zip with <platform> one of darwin-aarch64, darwin-x86_64, linux-aarch64, linux-x86_64
Reason Fix
it is not a zip archive, it holds no packed murmur.yaml, its packed murmur.yaml is invalid Rebuild the artifact with mur build
its packed name '<name>' … Give the artifact a valid name: — see Artifact names
its packed version '<v>' is not a semantic version, … is reserved Set version: to a semantic version such as 1.0.0
its file name does not match its packed name … and version … Rename the file to <name>-<version>.mur.zip, or rebuild it
a native payload names its platform Name the file <name>-<version>-<platform>.mur.zip
a wasm payload runs on every platform, a static payload … Drop the platform from the file name
its wasm payload cannot be read as a component: it has no root .wasm entry …, … its root .wasm entries are ambiguous: … A wasm payload carries its component as capsule.wasm or as its one root *.wasm; rebuild it with mur build
its wasm payload cannot be read as a component: its root .wasm entry <file> is a core module, not a component Build the payload as a component (--target wasm32-wasip2), then mur build
its wasm payload cannot be read as a component: its root .wasm entry <file> does not parse as a component: … The file is not wasm, or is truncated; rebuild it
its wasm payload cannot be read as a component: its root .wasm entry <file> is over the <N>-byte decompression cap The component is larger than MURMUR_MAX_ARTIFACT_DECOMPRESSED_BYTES allows
it packs runtime '<a>' …, and <file> of the same <name>@<version> packs runtime '<b>' Build every platform's payload of one version from the same murmur.yaml
its packed murmur-build.json is not JSON …, … sets no built_at_level, … sets built_at_level to <value>, which is not a non-negative integer Rebuild the artifact with mur build, which writes the file
its packed murmur-build.json sets built_at_level <a>, and <file> of the same <name>@<version> sets built_at_level <b> Build every platform's payload of one version with the same mur

A payload whose packaging is wasm must carry a component mur index can read, because the index records the interfaces it names, and an empty list would fit every mur:

error[E-IDX-001]: release/tool-w-0.1.0.mur.zip cannot be indexed: its wasm payload cannot be read as a component: its root .wasm entry capsule.wasm is a core module, not a component

A native payload and a skill carry no component and are indexed with no interfaces.

A directory with no .mur.zip directly inside it is refused too:

error[E-IDX-001]: release holds no .mur.zip file to index

E-IDX-002 — not a schema 2 artifact index

mur search, mur new --index and mur index --merge read only a schema 2 artifact index. A schema 1 index, whose schema_version is 1 as a string or a number, is refused with a hint for its publisher:

error[E-IDX-002]: artifact index https://github.com/ana/my-artifacts/releases/latest/download/artifacts-index.json is a schema 1 index
  hint: this is a schema 1 artifact index, which this mur does not read; its publisher regenerates it with `mur index <dir> --repo <owner>/<repo> --release <tag>`
Message ends with Cause
is a schema 1 index The index's publisher has not regenerated it with mur index
has schema_version <value>, and this mur reads only schema 2 Another schema version
is not valid JSON: … The file is not JSON, or is truncated
is not a valid schema 2 index: <location> <reason> A field breaks the format; <location> names it, such as artifacts[0].versions[1].assets is missing

mur install reads a source's index from its latest release, so a release with no artifacts-index.json, or one carrying a schema 1 index, is refused too, naming the artifact being resolved. Its publisher writes and uploads one — see How to publish your own artifact source:

error[E-IDX-002]: github:ana/artifacts publishes no artifacts-index.json on its latest release v2, so ana/tool-git@1.0.0 cannot be looked up
  hint: its publisher writes the index with `mur index <dir> --repo ana/artifacts --release v2` and uploads artifacts-index.json to release v2

E-IDX-003 — mur index was not given a usable source

--repo must be a GitHub repository <owner>/<repo> whose owner is a valid scope, and --release a tag with no whitespace. The scope comes from --repo by the rule mur install github:<owner>/<repo>@<tag> uses, so the reserved scopes local and murmur are refused, except murmur for murmur-nexus/default-artifacts:

error[E-IDX-003]: --repo 'murmur/tools' would index under 'murmur', a reserved scope
  hint: 'local' names artifacts installed from a .mur.zip file and 'murmur' names those from murmur-nexus/default-artifacts; no GitHub owner can claim either

An index given to --merge must have the scope --repo names:

error[E-IDX-003]: --merge previous.json indexes scope 'bob', and --repo ana/tools names scope 'ana'

Registry errors

E-REG-001 — the source does not list the artifact

The scope's source answered, and its index lists no artifact of that exact name, no such version, or no payload of the version for this platform:

error[E-REG-001]: could not resolve ana/tool-git@2.0.0: the index of github:ana/artifacts lists ana/tool-git at 1.0.0, 0.9.0
  hint: name one of those versions, or omit the version to install the newest that fits this mur: mur install ana/tool-git
Message Fix
the index of <source> (release <tag>) lists no <scope>/<name> Check the name. A name matches exactly: ana/tool-git is not ana/tool-git-validate
lists <scope>/<name> at <versions> Name one of the listed versions, or leave the version out
has no payload of it for <platform>; it publishes … Build it on this host with mur build and install the file, or ask the publisher to release that platform's asset
no version of <scope>/<name> in <source> fits WIT level <N>, the level this mur serves; the newest is … The reference named no version and no version fits; the hint links the WIT levels page, which lists the mur release serving each level
the index of <source> lists it as <file> on release <tag>, and … The index and the releases disagree; the publisher regenerates the index

An explicit version that is listed but does not fit is refused with E-WIT-LEVEL-001.

A local/ artifact missing from the store is refused with the same code; no source serves the local scope:

error[E-REG-001]: artifact local/tool-gi@1.0.0 is not in the store, and no source serves the 'local' scope
  hint: a local/ artifact is installed from its .mur.zip file: mur install ./tool-gi-1.0.0.mur.zip

A launch whose murmur.yaml names an artifact neither store holds is refused at staging:

error[E-REG-001]: artifact ana/tool-gi@1.0.0 is not in the store
  hint: install it: mur install ana/tool-gi@1.0.0

E-REG-002 — an artifact's bytes or name do not match

Installed bytes that do not hash to the sha256 murmur.lock records for them are refused:

error[E-REG-002]: artifact integrity check failed for ana/tool-x@1.0.0
  hint: artifact on disk does not match murmur.lock — re-publish or delete the lock

A payload fetched from a source is checked against the source's index before anything is stored. A digest or a packed version: other than the index lists is refused:

error[E-REG-002]: could not resolve ana/tool-git@1.0.0: tool-git-1.0.0.mur.zip from github:ana/artifacts has sha256 bbe05ded0b5b42985748292a0d20b482c73d5e7f346a275c80f0d45b4386a038, and its index lists sha256 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
  hint: the payload is not the one the index describes, so nothing was stored; its publisher regenerates the index, or the download was altered on the way
error[E-REG-002]: tool-git-1.0.0.mur.zip from github:ana/artifacts packs version '1.0.1', and its index lists it as ana/tool-git@1.0.0
  hint: the asset was built before its version was set, so nothing was stored; its publisher rebuilds it and regenerates the index

An artifact is filed under the bare name its own murmur.yaml declares — see Store layout. Installing it under a full name whose bare part differs is refused, and nothing is written to the store:

error[E-REG-002]: artifact ana/tool-x@1.0.0 declares name 'tool-y' in its murmur.yaml, so it cannot be filed as ana/tool-x
  hint: an artifact is filed under the name its own murmur.yaml declares; install it as ana/tool-y

E-REG-006 — a source did not answer the lookup

mur install, mur install --all-platforms and mur deploy run ask the source of the artifact's scope. When that source fails to answer a request, the install fails with E-REG-006 rather than E-REG-001: the source may publish the artifact. The message gives what the source returned:

error[E-REG-006]: could not look up murmur/driver-anthropic@1.0.0: github:murmur-nexus/default-artifacts did not answer — rate limited by GitHub (HTTP 403, x-ratelimit-remaining: 0): API rate limit exceeded for 203.0.113.7. — resets in about 41 minutes
  hint: set GITHUB_TOKEN to any GitHub token, for example `export GITHUB_TOKEN=$(gh auth token)`, or point the source's `token:` in config.yaml at a variable you already export, such as `token: ${GH_TOKEN}` — GitHub allows 60 unauthenticated API requests an hour and the first artifact of a scope can spend five; see https://docs.murmur.nexus/reference/installing-artifacts/

The parenthesis after rate limited names what identified the rate limit:

Shown Identified by
(HTTP 429) The status alone
(HTTP 403, x-ratelimit-remaining: 0) GitHub's rate-limit header
(HTTP 403, retry-after header) A retry-after header, sent with GitHub's secondary rate limits
(HTTP 403, response body says rate limit) Only the response text; no rate-limit header was present

An explicit mur install github:<owner>/<repo>@<tag> reports a rate limit the same way.

After did not answer — Meaning Fix
rate limited by GitHub, requests sent without a token The anonymous limit for this IP address is spent Export GITHUB_TOKEN, or set token: on the source — see Rate limits
rate limited by GitHub, requests sent with a token The token's own limit is spent Retry after the reset the message shows
HTTP 401 or HTTP 403 without rate limited GitHub refused the token — for example, an organization's SAML enforcement, or a revoked token Authorize or replace the token
HTTP 5xx, request failed: … GitHub or the network did not answer Retry

E-REG-007 — a command-line artifact name is not <scope>/<name>

mur install <ref> and mur run --capsule take an artifact's full name, <scope>/<name> — see Artifact names. A bare name is refused with a hint naming its scoped forms:

error[E-REG-007]: 'tool-edit' is not a full artifact name: an artifact is named <scope>/<name>
  hint: name it with its scope: 'murmur/tool-edit' for a Murmur default artifact, '<owner>/tool-edit' for one published from github.com/<owner>, or 'local/tool-edit' for one installed from a .mur.zip file

A name with more than one /, or a part the name rule refuses, is refused with the reason and no hint:

error[E-REG-007]: 'a/b/c' is not a valid artifact name: expected exactly one '/', between the scope and the name

local and murmur are reserved scopes, so a github: reference whose owner is either is refused:

error[E-REG-007]: github:local/repo cannot be installed: 'local' is a reserved scope
  hint: 'local' names artifacts installed from a .mur.zip file and 'murmur' names those from murmur-nexus/default-artifacts; no GitHub owner can claim either

E-REG-008 — no source serves the scope

Each scope has one source. murmur is built in; any other scope needs a registry.sources entry naming a repository its owner publishes from. A scope with none is refused before any request:

error[E-REG-008]: no source serves the 'ana' scope, so ana/tool-x@1.0.0 cannot be looked up
  hint: add the GitHub repository 'ana' publishes from to ~/.murmur/config.yaml or the project's .murmur/config.yaml:
    registry:
      sources:
        - repo: ana/<repository>

E-REG-009 — a source's index names another scope

A source answers only for the scope of its owner, so the scope of its index must be that scope. An index naming another one is refused, and nothing is downloaded beyond it:

error[E-REG-009]: could not resolve ana/tool-x@1.0.0: github:ana/claims-bob publishes an index for scope 'bob', and it serves scope 'ana'
  hint: a source answers only for the scope of its owner, so nothing was installed; its publisher regenerates the index with `mur index <dir> --repo ana/claims-bob --release <tag>`

Only murmur-nexus/default-artifacts may publish an index for murmur:

error[E-REG-009]: could not resolve murmur-nexus/tool-x@1.0.0: github:murmur-nexus/other publishes an index for scope 'murmur', which only murmur-nexus/default-artifacts serves; this source serves scope 'murmur-nexus'

E-REG-010 — a scope's owner has changed

A scope is tied to the numeric id of the GitHub account that owns it — see Owner identity. An install whose source answers with another id than the one recorded for the scope is refused before anything is downloaded, written or pinned:

error[E-REG-010]: could not resolve ana/tool-git@1.0.0: the owner of scope 'ana' has changed: github:ana/artifacts is now owned by GitHub account id 2222, and murmur.lock records owner id 1111 (entry ana/tool-git)
  hint: the GitHub account that owned the scope was renamed and its name registered again by someone else, or the repository changed hands; if you trust the new owner, delete the 'ana' entries from murmur.lock and install again
Recorded in Message
murmur.lock … murmur.lock records owner id <id> (entry <name>)
The .meta.json of a payload the global store serves to a project install <name>@<version> in the global store was installed from GitHub account id <id>, and murmur.lock records owner id …
mur itself, for murmur … scope 'murmur' is owned by GitHub account id 190196424, the owner of murmur-nexus/default-artifacts, and github:murmur-nexus/default-artifacts answered with owner id <id>

To trust the new owner, delete the scope's entries from murmur.lock and install again. The murmur scope's id cannot be changed: a different one means the request did not reach GitHub's own murmur-nexus/default-artifacts, for example through MUR_GITHUB_API_BASE.

E-REG-011 — a payload ships a malformed models: table

mur install — from a file, GitHub, the global store or a manifest's dependencies — and mur publish read the payload's packed murmur.yaml before anything is stored. A models: table E-BLD-005 would refuse is refused here too, and neither the store nor murmur.lock is written:

error[E-REG-011]: local/murmur-driver-anthropic@0.1.0 cannot be stored: its murmur.yaml ships a malformed models: table: murmur.yaml: field 'models.test-model.max_input_tokens' is 1050001, above this entry's context_window of 1050000; an input ceiling is at most the window
  hint: the driver's own murmur.yaml is at fault; use a release of the driver whose models: table is well formed — mur build refuses the same table with E-BLD-005

A payload with no readable murmur.yaml declares no table, and is stored.


Registry warnings

mur run and mur doctor print non-fatal warnings about what an artifact store holds. Each one carries a W-REG-NNN code and a link back to its section on this page, and none of them changes the exit code of mur doctor.

W-REG-001 — installed native artifact with no recorded platform

warning[W-REG-001]: murmur/tool-git@0.4.2 is a native artifact with no recorded platform — it resolved from the generic store path, where every host resolves the same payload
  Fix: mur install murmur/tool-git@0.4.2
  (https://docs.murmur.nexus/reference/diagnostics/#w-reg-001)

A native tool is a different binary per platform, so the store files it at <name>-<version>-<platform>.mur.zip with a metadata sidecar of the same name. This artifact is at the untagged <name>-<version>.mur.zip instead, which is where an install that recorded no platform puts it. Two consequences:

  • Every host resolves that one payload, whichever platform it was built for.
  • Installing a second platform into the same version directory would overwrite it rather than sit beside it.

Reinstalling refiles the payload under this host's platform tag: mur install <name>@<version>. There is no separate migration command, and nothing rewrites a store in place — mur install overwrites what is there.

mur doctor marks that artifact's checklist line, prints the same Fix: line, and counts it as a warning rather than an error, so a store holding an untagged native payload does not fail a CI pre-flight check.

W-REG-002 — a capsule in the spawn closure could not be inspected

warning[W-REG-002]: 1 capsule in the spawn closure could not be inspected, so what it declares is absent from the report above: ghost-worker
  The walk not being able to read a capsule is not evidence that a run fails, so this does not change the exit code.
  (https://docs.murmur.nexus/reference/diagnostics/#w-reg-002)

mur doctor walks a capsule's capabilities.spawn.allow closure to report what every capsule in it needs from the operator's environment. A capsule it cannot open contributes nothing to that report, so the report is incomplete. The matching stdout block names each one and why:

  could not inspect 1 of 3 capsules in the spawn closure:
    - local/ghost-worker (declared by root-capsule@0.0.1): not installed in the project or global store

Three reasons:

Reason What it means
not installed in the project or global store Neither store holds any version of that name
N versions installed (…) and nothing pins which one a run would get More than one version is installed and no murmur.lock entry pins one; no version is chosen, because there is no latest alias and guessing would report the wrong capsule's requirement as fact
installed but unreadable A version resolved, but its archive or packed murmur.yaml could not be read

Install the capsule, or pin its version in murmur.lock, and mur doctor folds its declarations into the report.

W-REG-003 — an installed artifact does not fit the WIT level this mur serves

  installed:
    ana/stale-hook@0.3.0   global    built at none (derived)   does not fit level 3
  ⚠  ana/stale-hook@0.3.0   global    exports murmur:hook/lifecycle@0.8.0 — this mur serves murmur:hook@0.9.0
  mur run refuses these at launch (warning[W-REG-003], https://docs.murmur.nexus/reference/diagnostics/#w-reg-003)

An artifact is built against one version of each Murmur interface it uses, and this mur serves the one set of versions its WIT level lists. An installed artifact that does not fit that level does not load. The common cause is upgrading mur past an interface change while the store still holds artifacts built for the older mur.

Reinstalling the same version fetches the same build. Install a release of the artifact built against the version this mur serves, and pin it in murmur.yaml if the project declares it.

mur doctor prints this warning for every stale artifact in the project and global stores that this project does not declare. A stale artifact the project does declare fails the checklist instead, because the next mur run refuses it.


Roster errors

A roster.yaml is admitted whole or refused with one of these codes. Every message names roster.yaml. Every code except E-ROS-001 names the member at fault, apart from an E-ROS-002 for a roster with no entry member, where no single member is at fault. The order admission checks them in is in Admission order.

E-ROS-001 — the roster is missing or malformed

The message names the key path, such as members[1].version, and the member's name when the fault is inside a member entry:

error[E-ROS-001]: roster.yaml: members[1].version (member 'coder'): 'latest' is a reserved alias, not an exact version; pin the version the member runs
Fault Fix
YAML syntax error Fix the YAML at the line and column shown
unknown key Remove or correct the key. A misspelt reachability is refused rather than read as no reachability
is required, or a wrong type Write the field with the type in Fields
roster_version other than 1 Write roster_version: 1
members empty List at least one member
A name that does not match ^[a-z][a-z0-9-]{0,31}$ Use lowercase letters, digits and -, starting with a letter, at most 32 characters
A capsule that is a bare name, such as crew-lead Name the capsule by its full name, as mur list shows it: local/crew-lead for one installed from its .mur.zip file
A capsule that is not a valid artifact name Name the capsule as mur list shows it
A version that is blank, latest, stable or edge Write the exact installed version
A reachability string other than all Write all or a list of rules
A rule with an empty to, or one listing its own from List at least one other member

E-ROS-002 — no entry member, or several

Exactly one member has entry: true. With none, the message says so; with several, it names every member that claims it. Mark the member that receives the formation's task, and only that one. See The entry member.

E-ROS-003 — two members share a name

Give each member its own name. Two members may run the same capsule under different names. This is checked before the entry member, so a roster with a duplicate is refused with E-ROS-003 whatever its entry lines say.

E-ROS-004 — a rule names no member

The message names the rule's from and the name that matches no member, which is from itself when the rule's from is unknown. Add the member to members, or correct the name in the rule.

E-ROS-005 — a rule calls a member that does not serve peers

Every member in a rule's to must declare exports.peer_tasks.accept: true in its own murmur.yaml. Declare it and rebuild and reinstall the capsule, or remove the member from every rule's to. reachability: all never pairs a member that does not serve peers, so it never raises this code.

E-ROS-006 — a public door in a roster with peer traffic

When the roster has at least one edge, every member declares network.authentication, including members on no edge. Declare it in the member's murmur.yaml and rebuild and reinstall the capsule, or remove the roster's reachability. A capsule that declares network.authentication cannot also declare capabilities.spawn.allow (E-MAN-003). See Authentication.

E-ROS-007 — a member cannot be resolved

The message names the member and its capsule@version. The capsule is in neither the project store nor the global store at that version, its bytes do not match the hash the store recorded for them, or its packed murmur.yaml cannot be read or parsed. Install the capsule at that version with mur install <capsule>@<version>, or change the member's version to one mur list shows.

E-ROS-008 — a rule calls the entry member

error[E-ROS-008]: roster.yaml: the reachability rule from 'coder' lists the entry member 'planner' in `to`, but the entry member is never called by a peer: it takes the formation's tasks from the launch or from a client at its door
  hint: remove 'planner' from that rule's `to` in roster.yaml. For 'coder' to report to 'planner', write the edge the other way — `- from: planner` with `to: [coder]` — so 'planner' hands 'coder' its work with call-member and 'coder''s answer comes back into 'planner''s own task; 'coder' then needs exports.peer_tasks.accept: true in its murmur.yaml

The message names the rule's from and the entry member. The entry member is never called by a peer: its tasks come from the launch's --task or from a client at its door, and no member is issued a credential for it. See The entry member.

Turn the edge around: remove the entry member from the rule's to, and write a rule from: the entry member with the caller in to. The entry member then hands the caller its work with call-member, and the caller's answer comes back into the entry member's own task. The caller, as a callee, then needs exports.peer_tasks.accept: true in its murmur.yaml. reachability: all never pairs a member with the entry member as its callee, so it never raises this code.


Roster warnings

mur run --roster prints these on stderr after admitting the roster and before the formation launches. mur doctor prints them in its Roster block on stdout and counts each as a warning. Each one carries a W-ROS-NNN code and a link back to its section on this page. None of them refuses the roster or changes an exit code.

W-ROS-001 — more members may call a member than it holds

warning[W-ROS-001]: roster.yaml lets 10 members call 'reviewer' (w01, w02, w03, w04, w05, w06, w07, w08, w09, w10), but it holds 2 tasks at once — one running and lifecycle.queue_depth: 1 waiting — so a call that arrives while it is full waits and is offered again, and is rejected only if it stays full until the call's deadline; lifecycle.queue_depth: 9 would hold them all (https://docs.murmur.nexus/reference/diagnostics/#w-ros-001)

A member holds a fixed number of tasks at once, set by its own lifecycle. A call that arrives while it holds that many waits: the calling member's call-member offers the task again with backoff until the member takes it, and ends the call rejected only if the member stays full until the call's deadline. A member the table below gives 0 tasks never takes one, so every call to it ends rejected at its deadline. See A busy member.

lifecycle.task_acceptance Tasks held at once
queue queue_depth + 1: one running and queue_depth waiting
single 1
none 0

Callers are counted from the roster's expanded reachability, all included, one per calling member: a member holds at most one unanswered call to a given member. The warning names the member, how many members may call it and which, what it holds, and the queue_depth that holds every caller.

Fix it with one of:

  • raise lifecycle.queue_depth in the called member's murmur.yaml to the value the warning names;
  • under single or none, set task_acceptance: queue with that queue_depth;
  • narrow the roster's rules so fewer members may call it.

A member's own background work — a demoted shell command's completion, a delegation's outcome — takes a queue slot too, and is not counted here.

Nothing is refused, because the calls may never overlap: a roster whose callers take turns runs without a rejection.


Runtime warnings

A session prints non-fatal warnings about what a turn did while the loop was running. Each one carries a W-RUN-NNN code and a link back to its section on this page. The session still produces a result, still ends ok, and mur run still exits 0.

These are decided mid-session, so they go to stderr alone. logs/bootstrap.log covers the launch, which is over by the time a turn happens.

W-RUN-001 — a turn stopped at the output cap

[capsule-runtime] warning[W-RUN-001]: this turn stopped at the inference.max_tokens output cap of 256 tokens and was cut off; the task goes on with another turn
  (https://docs.murmur.nexus/reference/diagnostics/#w-run-001)

The provider reported stop_reason: "max_tokens": it generated the whole budget inference.max_tokens allowed and stopped mid-reply. The cap named in the warning is the value that was in force — the manifest's inference.max_tokens, the driver's models: entry for the model, or a controller's change.

The warning prints once per capped turn, worded by what the turn does to the task:

Outcome Warning body
The task goes on this turn stopped at the inference.max_tokens output cap of <N> tokens and was cut off; the task goes on with another turn
The task's last allowed turn under inference.max_turns this turn stopped at the inference.max_tokens output cap of <N> tokens, so out/result.txt holds a fragment rather than a finished answer
The second turn in a row with no text and no tool call this turn stopped at the inference.max_tokens output cap of <N> tokens with no text and no tool call, the second in a row, so the task ends failed

What each outcome leaves behind:

Surface The task goes on Last allowed turn Second empty turn in a row
out/result.txt in the session's records directory, and out/result_<task-id>.txt under mode: threaded Untouched by this turn; the turn that ends the task writes it The model's partial text, then a marker naming inference.max_tokens and the cap error: and the failure reason
The task's response artifact that GetTask reports, and the task output an on-task-end hook reads — The same marked text The failure
The conversation record The turn's thinking and text, with "truncated": true, then the runtime's user message stating the cut The turn's thinking and text, with "truncated": true The turn's thinking and text, with "truncated": true, when it had any
The trace's inference event "decision": "capped", "stop_reason": "max_tokens" The same The same
The trace's task_end capped_turns counts the turn The same; exit_status "ok" The same; exit_status "failed", after a task_failed with cause output_cap

A tool call in a capped turn is never run, in any outcome: its arguments may be cut. The user message names it, and the record keeps only the turn's thinking and text. A turn cut at the output cap gives the message's exact text.

inference.max_turns: 1 makes every capped turn the last: the task ends ok with the fragment. The output_cap failure's reason names both turns and the cap:

two turns in a row (turn 0 and turn 1) stopped at the inference.max_tokens output cap of 256 tokens with no text and no tool call

Raising inference.max_tokens gives a turn room to finish. A model that spends the whole cap reasoning, with no text and no tool call, is the case the output_cap failure stops: lower its reasoning effort, or raise the cap.

W-RUN-002 — an untested harness version

[capsule-runtime] warning[W-RUN-002]: the harness reports version 'my-cli 0.9.0', which this process driver was not tested against (tested against 1.0.0)
  (https://docs.murmur.nexus/reference/diagnostics/#w-run-002)

Before each run, mur run runs the harness binary with the version arguments the process driver's describe() names, and compares what it printed against the versions that driver release was tested against. A harness whose flags or output format have moved since may be driven wrongly in ways that only show up mid-run.

The version could also not be read at all, in which case the warning says why:

Reason Message ends
The binary exited non-zero it exited non-zero
It did not answer within 10 seconds it did not answer within 10s
It printed nothing it printed nothing
It could not be run it could not be run: <error>

Nothing is refused: the run proceeds on the driver as written. The trace carries the same text as a harness_warning event, and harness_start.version_tested records the verdict.

W-RUN-003 — an unavailable driver choice

[capsule-runtime] warning[W-RUN-003]: driver choice 'gpt' (driver murmur/driver-openai, model gpt-5) is unavailable: its credential OPENAI_API_KEY was found in neither the global config's credentials: nor the environment, so a switch to it will be refused; the session runs on the primary
  (https://docs.murmur.nexus/reference/diagnostics/#w-run-003)

The gateway of an inference.alternates driver is keyed by a ${NAME} that neither credentials.<NAME> nor the launching environment holds. The primary's credential is what the launch needs, so the launch goes on:

Where What it shows
session_start.inference_choices The choice with available: false and credential_source: "none"
mur control show The choice marked unavailable
A switch to it Refused 409 credential_unresolvable — see Control surface

Set the credential and restart the capsule to make the choice available. A credential supplied at run time goes in control.secrets instead of the environment.

W-RUN-004 — a malformed input_schema

[capsule-runtime] warning[W-RUN-004]: the tool 'my-tool' declares an input_schema whose `required` is not an array of strings, so its calls are dispatched without the required-field check (https://docs.murmur.nexus/reference/diagnostics/#w-run-004)

A tool's input_schema cannot be read where the runtime checks a call's required fields. The tool stays callable and its calls run unchecked; the warning prints the first time the tool is called in a session, and not again.

The message says the schema's Means
root is not a JSON object The schema is an array, a string, a number or null rather than {...}
`required` is not an array of strings required is a string, an object or null, or one of its entries is not a string

Fix the schema in the tool's own murmur.yaml: required is a JSON array of property names. A field the tool supplies a default for belongs out of required — a listed field is refused when it is missing.

W-RUN-007 — a formation member with no lifeline

warning[W-RUN-007]: this session is a member of formation frm_0192a5b3c4d57e6f8a9b0c1d2e3f4a5b but was handed no lifeline, so it will not wind down when that formation ends; end it with `mur stop` (https://docs.murmur.nexus/reference/diagnostics/#w-run-007)

A mur run launched with MURMUR_FORMATION_ID set and no MURMUR_FORMATION_LIFELINE, other than a delegated child, is a formation member started by hand. It runs as any other mur run does, for as long as its lifecycle keeps it, and appears in mur ps. Nothing ends it when its formation ends; end it with mur stop. The warning prints once, at launch. Nothing is refused. See How a formation ends.

W-RUN-008 — a caller that cannot reach a member's door

[capsule-runtime] warning[W-RUN-008]: roster.yaml lets 'lead' call 'worker', but its capabilities.network.allow reaches no loopback http door at an unpinned port, so every call-member call will be refused; declare "localhost" (https://docs.murmur.nexus/reference/diagnostics/#w-run-008)

A member that roster.yaml lets call another has the call-member tool, but a roster rule grants a name and a credential, never network access. Each call is checked against the member's own capabilities.network.allow at the called member's real door, which a formation serves on loopback http at a port chosen at launch. Only an entry that matches localhost over http at every port reaches it:

Entry Reaches a member's door
localhost Yes
http://localhost No: a URL entry with no port means the scheme's default port, 80
localhost:8080, http://localhost:8080 No: the door's port is chosen at launch
127.0.0.1 No: a door's address names localhost

The warning prints once, at staging, to stderr and the member's logs/bootstrap.log. Nothing is refused: the member launches and may still be called, and each call-member call it makes fails in the same turn, naming capabilities.network.allow. Declare localhost in the member's capabilities.network.allow. See Giving a member work.

W-RUN-009 — a driver lists no limits for the model

[capsule-runtime] warning[W-RUN-009]: driver murmur/driver-openai@1.0.0 declares no limits for model 'gpt-5.6-luna' (driver choice 'primary'), so the session uses inference.max_tokens (16384) as its output cap and knows no context window: compaction does not run and no seed budget applies; set context.max_tokens to give it one (https://docs.murmur.nexus/reference/diagnostics/#w-run-009)

The choice's driver has no models: entry for its model, the manifest sets inference.max_tokens, and it sets no context.max_tokens. The launch goes ahead on the manifest's output cap, and for that choice:

Without a context window Effect
Compaction Does not run
An on-task-start seed, when the choice is the primary Refused with reason: "no_budget"

The warning prints once per such choice, at staging, to stderr and logs/bootstrap.log, and mur doctor prints the same text. A manifest that sets both inference.max_tokens and context.max_tokens needs nothing from the table and draws no warning. Set context.max_tokens to the model's context window, or install a release of the driver that lists the model.


Security warnings

mur run, mur build and mur doctor print non-fatal warnings about capability and enforcement gaps in a manifest or host. Each one carries a W-SEC-NNN code and a link back to its section on this page:

[capsule-runtime] warning[W-SEC-001]: capabilities.shell.allow is non-empty but this platform
has no kernel-level subprocess sandbox (Landlock/seccomp are Linux-only) — enforcement is
environment-only (synthetic HOME + credential env-stripping). This is permanent on this
platform. (https://docs.murmur.nexus/reference/diagnostics/#w-sec-001)

Where a warning is written depends on whether the session's records directory exists yet:

Warning Written to
W-SEC-001, W-SEC-002, W-SEC-003, W-SEC-005, W-SEC-010, W-SEC-021, W-SEC-023 — decided at launch stderr and the session's logs/bootstrap.log
W-SEC-006 to W-SEC-009, W-SEC-011 to W-SEC-019, W-SEC-024, W-SEC-025, W-SEC-027, W-SEC-028, W-SEC-029, W-SEC-030 — decided at staging, before the records directory exists stderr
W-SEC-004 — from mur build stderr
W-SEC-031 — decided mid-session, when the harness reports its session stderr
W-SEC-032 — decided at staging by mur run, by mur doctor, and by mur deploy run once the port is published stderr from mur run and mur deploy run; stdout from mur doctor
W-SEC-033 — decided at staging by mur run, and by mur doctor stderr

W-SEC-001 — No kernel sandbox on this platform

Fires when: capabilities.shell.allow is non-empty and the host resolves to the Environment-only tier — see Subprocess enforcement tiers for which hosts those are and why it is permanent there.

Why it matters: shell subprocesses on this host get environment-level protection only — a synthetic HOME and credential env-stripping. No kernel enforcement constrains what they can read, execute, or reach on the network.

What to do: treat capabilities.shell.allow and capabilities.network.allow as advisory on this platform, not a security boundary. For kernel enforcement, run the capsule on a host that resolves to an enforcing tier (see Subprocess enforcement tiers). If the capsule ingests untrusted external content, use the data/action phase-separation pattern instead of relying on the allowlists to contain a compromised subprocess.


W-SEC-002 — Landlock unavailable, filesystem scope unenforced

Fires when: capabilities.shell.allow is non-empty and the host resolves to the Seccomp-only tier — see Subprocess enforcement tiers.

Why it matters: filesystem reads and writes outside the capsule workdir are not kernel-enforced at all on this tier. Nor is exec: capabilities.shell.allow is enforced by granting the Landlock Execute right on exactly the allowlisted binaries, so without Landlock there is no exec mediation here and a shell subprocess can run any binary its uid can reach.

The fixed capsule device set is a Landlock rule list, so it does not apply here either — and the direction of that gap is worth being explicit about. Without Landlock there is nothing to grant and nothing to deny: a capsule here can open /dev/null, /dev/zero and /dev/urandom, but equally /dev/random, /dev/mem and any raw block device its uid can reach.

What survives on this tier is everything that needs no Landlock — the shell child's capability drop and the socket(2) domain denial, both described under What every Linux tier grants. A host stuck below kernel 5.13 is therefore not exposed to the /var/run/docker.sock escape.

What to do: upgrade the host kernel to move to the Full tier. Until then, treat filesystem scope and exec scope as advisory on this host — neither has a mechanism here. This is also why this tier cannot reach the scoped containment class.


W-SEC-003 — bash bypasses the network allowlist

Fires when: capabilities.shell.allow contains the literal entry "bash" and capabilities.network.allow is non-empty, on a host that resolves to the Environment-only tier (see W-SEC-001). On the enforcing tiers a bash subprocess's own outbound connections are constrained by the same allowlist, so this warning does not fire there.

Why it matters: capabilities.network.allow constrains requests the runtime itself makes (outbound HTTP from tool and driver components). It does not constrain a bash subprocess's own outbound connections on this tier — bash can reach any host regardless of what network.allow declares.

Maximum-risk combination: bash in shell.allow combined with any external-fetch capability (network.allow, or a tool/driver artifact that fetches independently) gives a capsule both exposure to untrusted content and unchecked shell authority to act on it — see the threat model for the full picture alongside prompt injection.

What to do: run on a host that resolves to an enforcing tier (see Subprocess enforcement tiers), or avoid pairing bash with a non-empty network.allow on platforms without one. Declaring sh or zsh instead only silences the warning: the check matches the literal string bash, and the exposure is the same.


W-SEC-004 — Literal secret in manifest

Fires when: mur build scans murmur.yaml and finds a credential-shaped field — one whose key contains api_key, token, secret or password — holding a string that is not a ${VAR_NAME} reference and is either longer than 8 characters or begins with a known API-key prefix (sk-, sk-ant-).

Why it matters: a literal secret in murmur.yaml ships inside the built artifact and is easy to accidentally commit to version control.

What to do: replace the literal value with a ${VAR_NAME} reference and inject the real value via environment at run time. The build still succeeds — this is a warning, not a blocker — but the artifact should not be published or committed until the literal is removed.


W-SEC-005 — Linux kernel enforcement is in force

Fires when: capabilities.shell.allow is non-empty and the host resolves to the Full or the Sealed tier (see Subprocess enforcement tiers). The strongest tiers warn rather than stay silent, because each is a scope with documented exceptions and silence would imply it has none.

What the message says. On the Full tier it names what the Landlock scope, the fixed device set, the socket(2) domain deny and the default-deny syscall allowlist cover — see Subprocess enforcement tiers for the full grant. On the Sealed tier it names what the composed root adds and states its one exception: /proc is a bind of the host's rather than a masked private procfs, because mounting one unprivileged needs a PID namespace this tier does not create, so host process metadata stays visible inside the root exactly as it is under scoped.

The one documented exception a manifest controls. capabilities.network.unix_sockets: true re-widens the AF_UNIX half of the domain deny. Nothing else on the list has an opt-out:

socket(2) domain Default Can a manifest widen it?
AF_UNIX denied (EACCES) yes — capabilities.network.unix_sockets: true
AF_NETLINK denied (EACCES) no
AF_PACKET denied (EACCES) no
AF_INET, AF_INET6, everything else unaffected governed by capabilities.network.allow — TCP and UDP by IP destination

That key is coarse on purpose: it is a whole address family, not a per-socket-path allowlist, so declaring it true re-exposes every unix socket the process can reach — /var/run/docker.sock, which is host root, included. Landlock cannot substitute for it at any ABI, because ABI v6's LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET scopes abstract unix sockets only and docker.sock is a pathname socket. Declare it only when a shell tool genuinely needs a local daemon socket. AF_UNIX is also how glibc's NSS reaches nscd and how syslog(3) reaches /dev/log, so the default deny is the mechanism on this list most likely to interfere with an existing workload.

Side effect worth knowing about: the shell child's capability drop also removes CAP_DAC_OVERRIDE from a root-run capsule's subprocess, so it no longer bypasses ordinary file-permission checks. That is intended: a shell step that relies on root's usual "can read anything" posture fails under it.

What to do: keep shell.allow, network.allow and filesystem.scope as narrow as the task genuinely needs, and don't run mur run as root if you can avoid it — a non-root capsule never had the CAP_MKNOD the workdir device-node restriction exists to backstop.


W-SEC-006 — Inert sub-key in a hook's capabilities: block

Fires when: a runtime: hook artifact entry's capabilities: block declares shell, spawn, env, limits.memory_bytes, limits.table_elements, limits.instances, resources or containment. Per-hook grants (see artifacts[].capabilities) read only network, filesystem, state, task_io, conversation and limits.deadline_seconds — the others are structurally accepted but nothing enforces them per-hook. Each is named by its full key:

[capsule-runtime] warning[W-SEC-006]: hook 'compactor' declares capabilities.limits.memory_bytes which the runtime does not apply per-hook — only capabilities.network, capabilities.filesystem, capabilities.state, capabilities.task_io, capabilities.conversation and capabilities.limits.deadline_seconds govern a hook (https://docs.murmur.nexus/reference/diagnostics/#w-sec-006)

Why it matters: an operator who declares, say, capabilities.shell.allow on a hook entry expecting it to scope that hook's shell access would otherwise have no signal that the runtime never reads it there — it is silently inert rather than rejected.

What to do: remove the inert sub-key from the hook's entry. Shell, spawn, env, the memory, table and instance limits, resources and the containment floor are capsule-wide concerns — declare them in the top-level capabilities: block instead. See Hook capabilities for the full rules on what a per-hook grant does and does not cover.


W-SEC-007 — Per-artifact network entry outside the capsule ceiling

Fires when: a runtime: tool or runtime: driver entry's capabilities.network.allow names an entry the capsule-wide top-level capabilities.network.allow does not itself allow. Per-artifact capabilities narrow (see Tool and driver capabilities): the effective grant is declaration ∩ ceiling, so the uncovered entry is dropped from that artifact's grant rather than granted. Staging continues.

A bare host (api.example.com) is broader than a scheme-bound ceiling entry (https://api.example.com) because it spans both schemes and every port, so it is not covered and will be dropped — this is the most common way to hit this warning by accident.

Why it matters: the artifact ends up with less access than the entry asked for. Nothing is widened, so this is never an escalation — but a tool that silently cannot reach a host it was "granted" fails in a way that is hard to trace back to the manifest.

What to do: either add the host to the capsule-wide capabilities.network.allow (if the whole capsule should be able to reach it), or make the per-artifact entry at least as specific as the ceiling entry it should sit under, or delete it if the drop was what you actually wanted.


W-SEC-008 — Unapplied per-artifact grant on a tool or driver

Fires when: either

  • a runtime: tool/runtime: driver entry's capabilities: block declares shell, spawn, env, limits, resources or containment — a per-artifact grant only reads network, filesystem and state (W-SEC-006 is the hook-side twin of this case, where task_io is honored as well); or
  • a runtime: tool entry with a native (non-WASM) implementation declares any capabilities: sub-block other than state. A native tool runs under the capsule-wide capabilities.filesystem, capabilities.network.allow, capabilities.network.unix_sockets and capabilities.shell.allow, enforced by the same sandbox as a shell command; a per-artifact block does not narrow them. capabilities.state is the one sub-block that applies to a native tool (A native tool's own config and store), so a block that holds only state prints nothing.
[capsule-runtime] warning[W-SEC-008]: artifact 'local/tool-fixture' declares capabilities.network but ships a native implementation — capabilities.state is the one sub-block that applies to a native tool, and the rest run under the capsule-wide capabilities, enforced by the same sandbox as shell commands, which this block does not narrow (https://docs.murmur.nexus/reference/diagnostics/#w-sec-008)

Why it matters: a declared-but-unapplied grant reads like a scoped artifact. In the native case the tool keeps the whole capsule ceiling despite an entry that looks like it narrowed it.

What to do: for an inert sub-key, remove it and declare it in the top-level capabilities: block instead. For a native tool, keep state if it needs a store, narrow the capsule-wide block itself, or ship the tool as WASM if it needs grants narrower than the rest of the capsule's.


W-SEC-009 — Interpreter-runtime grant couples the capsule to a host layout

Fires when: the capsule's top-level capabilities.shell.interpreter_runtime declares one or more grants. Fires once per grant, from both mur run (at staging) and mur doctor.

Why it matters: an interpreter_runtime grant widens an already-allowlisted binary's Landlock scope to specific host directories outside the workdir so a path-based interpreter (e.g. CPython) can reach its standard library. A CPython or Perl interpreter needs no grant for its own import path: mur derives those directories from the interpreter itself. For any other interpreter the grant is what lets it run, and it couples the capsule to a specific host distro/interpreter-version layout: a grant naming /usr/lib/python3.11 stops resolving the moment the host ships Python 3.12, and a capsule that runs on Debian may not run on Alpine. The durable fix is capabilities.shell.staged_runtime, which bind-mounts a pinned runtime tree into the capsule's own composed root instead of reaching out to the host's; it requires an effective sealed floor, while interpreter_runtime works at scoped too.

What it grants, exactly: one Landlock rule per named directory, and nothing else. Each directory carries its own required list_dir:

list_dir Rights granted Effect
true Execute + ReadFile + ReadDir The directory's own entries are enumerable — what CPython's FileFinder needs for a sys.path entry
false Execute + ReadFile Files inside can still be opened by exact name, but the directory itself cannot be listed

There is no field that accepts a prefix and expands it, and ReadDir is never inferred — it is granted only where an author wrote list_dir: true, and only on that one directory, never on its parent or siblings. A directory not named in the manifest receives no rule at all.

What to do: for an allowlisted CPython or Perl interpreter, delete the grant unless it names a directory the interpreter does not report on its own import path, such as a virtual environment's site-packages; mur run --explain-scope lists what is derived under derived interpreter runtime. A grant naming a trust-store directory such as /etc/ssl is not needed when the capsule declares capabilities.network.allow: mur grants the host's trust store then, so delete it. Otherwise, name the narrowest set of directories that actually works — measure the real requirement with strace -f -e trace=openat,getdents64 <interpreter> -c "import ..." rather than guessing, and set list_dir: false on any directory you only open known files inside. Accept that the capsule is now pinned to this host's interpreter layout, or switch to capabilities.shell.staged_runtime if the capsule can run at an effective sealed floor — it carries no host-layout coupling and fires no W-SEC-009.

Parse-time rejections. A malformed interpreter_runtime fails mur run/mur doctor with E-MAN-003 at manifest parse time, naming the offending value:

  • a binary not present in the same block's shell.allow — this mechanism narrows filesystem access alongside an exec grant that already exists, and never itself grants exec
  • a dirs[].path that is not absolute (does not start with /)
  • a dirs[] entry that omits list_dir — enumerability is never inferred
  • an interpreter_runtime[] entry with an empty dirs list

W-SEC-010 — No aggregate bound on the subprocess tree

Fires when: the capsule can spawn a native subprocess by any route (capabilities.shell.allow, capabilities.spawn.allow, or a native-implementation artifact) and the host is not Linux — so no cgroup v2 scope can exist for its process tree.

Why it matters: capabilities.resources bounds subprocesses with two independent mechanisms, and only the weaker one survives on this platform. Per-process setrlimit(2) ceilings still apply, and still apply as hard limits the capsule cannot raise. But every aggregate bound — cgroup_memory_bytes, cgroup_pids_max, cgroup_cpu_percent, cgroup_io_bytes_per_sec — is a cgroup v2 feature, and cgroups are Linux-only. Nothing caps the subprocess tree's total memory, task count, or CPU.

RLIMIT_NPROC is not a substitute, and this is the specific reason cgroups are required. It is a per-uid ceiling, not a per-tree one: a fork bomb of distinct, short-lived processes that fork and exit faster than the count is observed slips past it in practice even when it is set correctly. Only a cgroup's pids.max bounds the tree as a whole. Two further gaps on macOS specifically: it has no RLIMIT_AS, and its RLIMIT_DATA is present in the headers but not enforceable (the kernel rejects any finite value with EINVAL), so capabilities.resources.memory_bytes has no effect there either — a subprocess's memory is bounded neither per-process nor in aggregate.

The failure mode is denial of service. A capsule that exhausts host resources has not read, written, or reached anything outside its granted scope.

What to do: run capsules that spawn subprocesses on a Linux host with systemd user cgroup delegation configured, where the aggregate bounds are real — see Verification for how those bounds are checked by hand. On this platform, treat capabilities.resources' cgroup_* fields as declared-but-inert and do not rely on them. This is permanent on this platform, exactly like W-SEC-001: a kernel without cgroups cannot grow them.

Note the asymmetry with Linux, which is deliberate: there, the same condition (can spawn a subprocess, no cgroup scope available) is a refused launch with E-RUN-012, not a warning — on Linux a missing scope is a host misconfiguration an operator can fix, while here it is a property of the platform.


W-SEC-011 — Executable workdir makes shell.allow advisory

Fires when: the manifest declares capabilities.filesystem.workdir_exec: true. Once, at staging, on stderr — from both mur run and mur doctor.

Why it matters: capabilities.shell.allow is enforced by granting the Landlock Execute right on exactly the allowlisted binaries' own paths and withholding it everywhere the capsule can write — above all its own work folder. workdir_exec: true gives that right back to the workdir. From then on, a binary the agent compiles, downloads, unpacks or renames inside its workdir executes regardless of what the allowlist says. There is no name check to defeat, because there is no name check: the kernel is granting Execute on the path, and the path is inside the granted directory.

This is a stated trade, not a defect. Compile-and-run workloads — a capsule that runs gcc/cargo build in its workdir and then executes the artefact — need it, and there is no narrower form of the grant that distinguishes "a binary this capsule compiled" from "a binary this capsule downloaded". What the runtime refuses to do is let the trade be silent.

What the runtime does about it, beyond this warning:

  • the capsule's achieved containment class is advisory, on every host — including a Landlock-capable one that would otherwise report scoped or sealed;
  • mur run --explain-scope prints workdir exec: true next to that advisory;
  • trace.jsonl's session_start event carries workdir_exec: true, so a completed session's record shows which guarantee was in force;
  • declaring it alongside capabilities.containment: scoped (or sealed) refuses the launch with E-CAP-003, before any registry pull, artifact compile or workdir creation. The refusal text names the manifest key rather than a host mechanism, because no host can satisfy the pair.

What to do: remove workdir_exec if the capsule does not genuinely need to run something it produced — that is the default, and it makes capabilities.shell.allow a boundary the kernel enforces rather than a convention. If the capsule does need it, keep the declared containment floor at advisory and treat shell.allow as documentation of intent, not as containment: use the data/action phase-separation pattern to bound what the capsule can be induced to build and run, and give it no network allowlist entry it does not need.


W-SEC-012 — A compiler driver's toolchain has no Execute grant

Fires when: the capsule's declared containment floor is sealed and capabilities.shell.allow names a known compiler driver — cc, gcc, g++, c++ — whose helper binaries have no grant carrying the Landlock Execute right. Once per uncovered helper, at staging, on stderr, before any registry pull or workdir creation. Also printed by mur doctor, which never launches anything.

Why it matters: cc compiles nothing. It forks and execs a front end (cc1 for C, cc1plus for C++), an assembler (as), a linker (ld) and a linker wrapper (collect2). Those are separate binaries, and none of them appears in cc's own DT_NEEDED closure — so the derivation that stages an allowlisted binary's shared libraries into the composed root stages none of them.

They are still present inside the root, because they live under /usr, which sealed bind-mounts read-only into every composed root. What they do not have is permission to run: that fixed tree is granted ReadFile + ReadDir and deliberately not Execute, because it is the one grant covering whole host trees the manifest never named — it has to make them readable without making them runnable. The result is a capsule where cc --version prints a version and the first real compile dies partway through, with an exec failure on a path that is demonstrably right there.

Example. A capsule declaring containment: sealed and shell.allow: [cc] on a Debian-family host gets, at staging:

[capsule-runtime] warning[W-SEC-012]: capabilities.shell.allow grants the compiler driver 'cc',
but its helper 'cc1' at /usr/libexec/gcc/x86_64-linux-gnu/13/cc1 has no grant carrying the
Landlock Execute right under the 'sealed' composed root — ...

What to do: declare an interpreter_runtime (or staged_runtime) grant for the driver, naming the directories its helpers live in. Both grant shapes carry Execute, which is exactly what is missing. Measure the directories on the host rather than guessing them:

cc -print-prog-name=cc1        # /usr/libexec/gcc/x86_64-linux-gnu/13/cc1
cc -print-prog-name=as         # `as` — deferred to PATH, i.e. /usr/bin/as

The warning itself names the helper's containing directory, so the common case is a copy-paste.

A driver that cannot be run to answer those questions gets W-SEC-029 instead, and no W-SEC-012 for the helpers it was not asked about.

Why this warns where E-CAP-006 refuses

Both fire only under a declared sealed floor, and both are about a shell.allow binary that starts and then cannot finish. They differ in what is known:

E-CAP-006 (refusal) W-SEC-012 (warning)
Trigger an allowlisted #! script with no covering grant an allowlisted compiler driver with an uncovered helper
Evidence categorical — a script's ELF closure is empty, so staging it stages nothing it imports heuristic — a -print-prog-name= probe over a fixed table of four driver names
Failure it predicts a missing-module error inside the root, not a denial an exec failure partway through a compile
Outcome launch refused before any registry pull launch proceeds, operator warned

A capsule may compile successfully without every probed helper — a link-only workload never reaches cc1 — and a driver family outside the table is not probed at all, so refusing a launch on this evidence would block capsules that would have worked.


W-SEC-013 — Unprivileged user namespaces are unrestricted host-wide

Fires when: AppArmor is enabled on this host and kernel.apparmor_restrict_unprivileged_userns is 0. Once per launch, on stderr:

Command Prints it
mur run Once, at staging
mur run --roster Once, from the launcher, after the roster is admitted and before any member starts
A formation member, or a sub-capsule a capsule delegates to or a plan step starts Never: the runtime that starts it sets MURMUR_HOST_WARNINGS_REPORTED=1 in its environment
mur doctor Always

A mur run started with MURMUR_HOST_WARNINGS_REPORTED=1 does not print it. Only the value 1 silences it, and only on stderr: session_start still records the grant.

Why it matters: capabilities.containment: sealed and the capsule network namespace both need an unprivileged user namespace, and on an AppArmor host something has to permit it. This warning is about which of the two permitting postures the host is in — they differ enormously in blast radius:

Grant What it permits
profile_confining mur alone creates unprivileged user namespaces; every other binary stays restricted
restriction_disabled_host_wide every program on the machine creates them

The other two values, and the line that prints them, are in Where the user namespace comes from.

Ubuntu 23.10 and later ship the restriction on precisely because unprivileged user namespaces are a recurring local privilege-escalation surface. Switching it off works, and on a host where no profile can be loaded it is the right answer — but it is not the configuration murmur ships, and a sealed result obtained that way is a different security statement from one obtained through the profile.

What the runtime does about it, beyond this warning: nothing is refused, and no exit code changes — mur doctor on a project whose artifacts all check out still exits 0. The grant is recorded in the three places it can be read back:

  • mur doctor prints an AppArmor / user namespaces block naming the grant;
  • mur run --explain-scope prints userns grant: in its Containment block, and --explain-scope --json carries userns_grant;
  • trace.jsonl's session_start event carries userns_grant next to containment_achieved, so a finished session's record shows which of the two mechanisms was in force.

What to do: if the narrow grant is available to you, take it —

# 1. restore the hardening, removing any drop-in that disables it
sudo rm -f /etc/sysctl.d/*-userns.conf
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=1

# 2. install and load the profile shipped with mur
sudo install -m 644 packaging/apparmor/mur-sealed /etc/apparmor.d/mur-sealed
sudo apparmor_parser -r /etc/apparmor.d/mur-sealed

Running a build out of a checkout, where the binary is ./target/debug/mur or ./target/release/mur and no shipped profile attaches to it, use sudo scripts/install-dev-apparmor.sh — it generates and loads the same grant for those two paths, so a from-source developer never needs the host-wide sysctl. Then re-run mur doctor and expect userns grant: profile_confining.

If the host genuinely cannot load a profile, keep the sysctl and keep this warning: it is a record of the posture, not a defect to silence.

The installed-profile comparison

mur doctor also compares /etc/apparmor.d/mur-sealed byte-for-byte against the profile the running mur build ships, and prints both SHA-256 digests when they differ. That is a file finding and nothing more: AppArmor loads from the kernel's policy cache, so a file can be edited without apparmor_parser -r ever running, and a loaded profile can outlive the file it came from. The userns grant line is the behavioural answer and stays the source of truth — the comparison never changes a containment class and never changes an exit code.

Local customisation belongs in /etc/apparmor.d/local/mur-sealed, which both shipped profiles include if exists and which is deliberately not hashed.


W-SEC-014 — a capsule-wide capabilities.state block grants nothing

Fires when: the capsule's own top-level capabilities: block declares state. Once, at staging, on stderr.

Why it matters: a durable state store is granted per artifact — it is the runtime: tool, runtime: driver or runtime: hook entry that receives the second directory. The capsule's own component is built with no artifact grant at all, so a top-level declaration reaches nothing: no directory is created and no state path exists for anybody. Without this warning the only signal is a store that never appears.

[capsule-runtime] warning[W-SEC-014]: capsule-wide capabilities.state is declared, but a durable state store is granted per artifact — nothing reads a top-level declaration, so no store was created and no 'state' preopen exists. Move the block onto the tool, driver or hook entry that needs it (https://docs.murmur.nexus/reference/diagnostics/#w-sec-014)

What the runtime does about it: nothing is refused and no exit code changes. The block is structurally valid, exactly as an inert shell block on a hook entry is (W-SEC-006), so it is reported rather than rejected.

What to do: move the block onto the artifact entry that needs the store —

artifacts:
  - name: murmur/tool-corpus
    version: 0.1.0
    runtime: tool
    capabilities:
      state:
        store: shey

Two artifacts that need the same store each declare it, with the same store: name. There is no capsule-wide form: sharing a store is written once per artifact, so reading any one entry tells you what that artifact reaches.


W-SEC-015 — retired

Nothing raises this code and nothing reuses it. A native tool receives its entry's config: block as MURMUR_ARTIFACT_CONFIG, as a WASM tool does — see A native tool's own config and store.


W-SEC-016 — a capsule-wide capabilities.conversation block grants nothing

Fires when: the capsule's own top-level capabilities: block declares conversation. Once, at staging, on stderr.

Why it matters: the murmur:conversation/read grant is per hook — it is the runtime: hook entry whose component imports the interface that receives it. The capsule's own component holds no artifact grant and compiles against a world with no such import, so a top-level declaration reaches nothing and no artifact can read the conversation record.

[capsule-runtime] warning[W-SEC-016]: capsule-wide capabilities.conversation is declared, but the murmur:conversation/read grant is per artifact — nothing reads a top-level declaration, so no artifact can read the conversation record. Move the block onto the hook entry that needs it (https://docs.murmur.nexus/reference/diagnostics/#w-sec-016)

What the runtime does about it: nothing is refused and no exit code changes, on the same terms as W-SEC-014.

What to do: move the block onto the hook entry that reads the record —

artifacts:
  - name: local/hook-recall
    version: 0.1.0
    runtime: hook
    capabilities:
      conversation:
        read: true

W-SEC-017 — read_only is advisory for an allowlisted interpreter

Fires when: capabilities.filesystem.read_only is non-empty and capabilities.shell.allow names an interpreter — a shell (bash, sh, zsh, fish, dash, ksh) or a general-purpose one (python, python3, perl, ruby, node, deno, bun, php, awk, gawk, mawk, lua, tclsh, Rscript). Once per such binary, at staging, on stderr.

Why it matters: the dispatch-time check reads a shell call's argv and its -c script body, and flags what it can positively identify as a write — a redirection, or an argument in a write-target position of a binary it knows. An interpreter's own file I/O is in neither: python3 -c "open(p,'w').write(x)" is one opaque argument that names no redirection and no recognized verb. The interpreter can construct a write into a declared read-only subtree, and it will not be refused.

[capsule-runtime] warning[W-SEC-017]: capabilities.filesystem.read_only is declared and capabilities.shell.allow includes 'python3', an interpreter that can construct a write the dispatch check cannot read — the declaration is advisory for that binary. It still holds for every tool call and for every shell command whose write the dispatch check can identify (https://docs.murmur.nexus/reference/diagnostics/#w-sec-017)

What the runtime does about it: nothing is refused and no exit code changes. The declaration still applies in full to every tool call and to every shell command whose write the dispatch check can identify, and every refusal is still recorded as protected_path_denied.

What to do: treat the declaration as advisory for the named binary and decide whether that is acceptable for this capsule. Removing the interpreter from capabilities.shell.allow closes the gap; keeping it does not weaken any other part of the declaration. See Read-only paths.


W-SEC-018 — a tool's input schema does not say where it writes

Fires when: capabilities.filesystem.read_only is non-empty and an installed tool's input_schema declares a path-shaped property (path, file_path, filepath, filename, file) or a destination-shaped one (dest, dest_path, destination, destination_path, target_path, output_path, out_path, new_path, to) that the key-name rules still judge. Once per such tool, at staging, on stderr, naming every one of those properties.

The decision is per property. Annotating one property answers for that property alone, so a tool that has declared some of its destinations keeps being asked about the rest. A schema-root murmur-destinations list, including the empty one, answers for the whole tool. A property inside a subtree declared murmur-opaque is never named, because the key-name rules do not descend there either.

murmur-opaque answers for a property only where the schema declares that property an object or an array. On a string property the dispatch check ignores it and keeps judging the value by key name, so that property is still named here.

Why it matters: with nothing declared, the dispatch-time check guesses which of a tool's inputs are filesystem destinations from those property names. The guess is wrong in both directions: a note the tool merely stores, carrying a {file, text} pair, is refused as a write, and a destination under a name no table carries is never checked.

[capsule-runtime] warning[W-SEC-018]: capabilities.filesystem.read_only is declared and the tool 'guessed-tool' declares the property 'file_path' with no murmur annotation in effect — calls naming it are judged by key name. Annotate a destination property with "format": "murmur-destination", any object the tool only stores with "format": "murmur-opaque", and a destination derived from another property — or the absence of any — with the schema-root "murmur-destinations" list (https://docs.murmur.nexus/reference/diagnostics/#w-sec-018)

With more than one such property the line names them all: declares the properties 'file', 'path' with no murmur annotation in effect — calls naming them are judged by key name.

What the runtime does about it: nothing is refused and no exit code changes. The named properties keep the key-name rules.

What to do: say in the tool's input_schema what each named property is. A tool usually needs more than one of these:

Declaration Says
"format": "murmur-destination" on a string property That property's value is a file the tool writes
"format": "murmur-opaque" on an object or array property That subtree is payload the tool stores, not filesystem intent
"murmur-destinations": ["{repo_path}/.murmur"] at the schema root The tool writes a fixed path under another input's value
"murmur-destinations": [] at the schema root The tool writes nothing named by or derived from its input

A property that is a destination under some input values and a read source under others — a repo written under checkout and read under log — cannot be declared either way. It falls back on its name: if the name is in one of the tables above it keeps the key-name rules and keeps firing this warning; repo is in neither, so a tool with that shape is contained there only by capabilities.filesystem.scope.

The warning is the tool author's to answer, not the operator's: a capsule cannot annotate a tool it installs, and there is no way to suppress it for one. The entry grammar is in Read-only paths.

W-SEC-019 — an unrecognized key in murmur.yaml

Fires when: murmur.yaml carries a key no field of the block it sits in claims. Once per such key, on stderr, from mur run and from mur doctor in identical words.

Why it matters: the manifest parser sets deny_unknown_fields nowhere, so a key it does not recognize is accepted and applied to nothing. Without this warning a misspelled capabilities.filesystem.read-only and a correctly spelled read_only produce the same silent, successful launch — with opposite protections in force.

The wording distinguishes the two causes. A key within edit distance of one the block does recognize is a spelling problem:

[murmur-artifact] warning[W-SEC-019]: unrecognized key 'read-only' in capabilities.filesystem — did you mean 'read_only'? The manifest has a spelling problem here: the key was parsed and ignored, so whatever it declared is not in effect. Correct it in murmur.yaml (https://docs.murmur.nexus/reference/diagnostics/#w-sec-019)

A key with no near neighbour is one this build does not know, which a newer mur may:

[murmur-artifact] warning[W-SEC-019]: unrecognized key 'quantum_teleport' at the top level — this build of mur does not recognize it and no key it does recognize there is close to it, so the key may come from a newer mur. It was parsed and ignored; nothing here says the manifest is misspelled (https://docs.murmur.nexus/reference/diagnostics/#w-sec-019)
Part of the line What it names
'read-only' The key, spelled as the manifest spells it
in capabilities.filesystem The block that held it, dotted. A per-artifact block carries its index: artifacts[0].capabilities.shell. A top-level key reads at the top level
did you mean 'read_only'? The nearest key that block does recognize. Absent when none is near

What the runtime does about it: nothing is refused and no exit code changes. The key is reported and dropped; every other key in its block still parses and still applies. Refusing would make a manifest written for a newer mur unloadable by an older one, which turns every optional-key addition into a breaking change across a fleet.

What to do: correct the spelling when a suggestion is offered. When none is, check whether the key belongs to a newer mur than the one running — mur --version against the release the manifest was written for — and upgrade rather than edit the manifest.

W-SEC-020 — retired

Nothing raises this code and nothing reuses it. A delegation's outcome is delivered into the task that made it on every lifecycle, and that task ends any sub-capsule still running when it ends — see How the outcome arrives.

W-SEC-021 — a declared I/O ceiling did not apply

Fires when: the session was given a cgroup scope and the io.max write for capabilities.resources.cgroup_io_bytes_per_sec against that scope did not succeed. Once per launch, on stderr and in the session's logs/bootstrap.log.

[capsule-runtime] warning[W-SEC-021]: the declared capabilities.resources.cgroup_io_bytes_per_sec ceiling did not apply to this session's cgroup scope, so this capsule's native subprocess tree has no I/O bandwidth bound; memory.max and pids.max are still enforced on the scope (declared 104857600 bytes/s): the filesystem mounted at /dev/shm is backed by `tmpfs`, which is not a block device, so no io.max ceiling can name one (https://docs.murmur.nexus/reference/diagnostics/#w-sec-021)

Why it matters: io.max names a block device by MAJ:MIN, and a filesystem with no block device behind it — tmpfs, overlayfs, FUSE and network mounts — has none to name. Because a default ceiling is always applied, every capsule that gets a cgroup scope has a declared I/O ceiling, and this warning is what says the ceiling is not on the scope.

What the runtime does about it: nothing is refused and no exit code changes. io.max is the one cgroup limit this runtime treats as non-fatal; memory.max and pids.max stay enforced on the scope and stay fatal on failure. The message names the limits in force: it reads memory.max, pids.max and cpu.max when capabilities.resources.cgroup_cpu_percent sets a CPU cap, and memory.max and pids.max when it does not. See Whether the I/O ceiling applied.

What to do: the same reason this warning carries is in io_max.reason, in mur run --explain-scope --json and in session_start.effective_grants in trace.jsonl. Move the project onto a block-device-backed filesystem if an I/O bound matters for this capsule; otherwise the warning is a report and the session is bounded by memory, pids and CPU as declared.

W-SEC-022 — retired

Nothing raises this code and nothing reuses it. A shell command that moves to the background returns a work id on every lifecycle, and the agent reads the command's state, waits for it or stops it by that id with background-command.

W-SEC-023 — the session is not addressable

Fires when: a session's running-capsule record could not be written: an agent session's once its A2A door is open, a script capsule's once its session starts. Once per launch, on stderr and in the session's logs/bootstrap.log.

[capsule-runtime] warning[W-SEC-023]: this session's record under ~/.murmur/running/ could not be written, so `mur watch` and `mur cancel` cannot reach it by session address — only by the URL it announces — and `mur token` cannot read the tokens of an authenticated door: failed to create the directory: Permission denied (os error 13) (https://docs.murmur.nexus/reference/diagnostics/#w-sec-023)

A script capsule's warning says what it loses instead:

[capsule-runtime] warning[W-SEC-023]: this session's record under ~/.murmur/running/ could not be written, so `mur ps` cannot list it and `mur-roost` cannot count it: failed to create the directory: Permission denied (os error 13) (https://docs.murmur.nexus/reference/diagnostics/#w-sec-023)

Why it matters: for a script capsule, the record is what lets mur ps list it and a mur-roost started while it runs count it. For an agent session, the record is what lets mur watch and mur cancel name this session from a terminal that never saw its URL. Without one, the address mur run printed is the only way back to the capsule, and it is gone as soon as that terminal is. It is also where mur token reads a token: a capsule declaring network.authentication whose record was not written has no token anyone can read, so its door refuses every caller.

What the runtime does about it: nothing is refused and no exit code changes. An agent session binds its port, serves its door and runs its tasks exactly as it would have; a script capsule runs its run to the end.

What to do: the message carries the reason the write failed, usually a ~/.murmur that is not writable or a home directory the host would not resolve. Restore write access to ~/.murmur, or set HOME to a directory this user owns, and the next launch records itself.

W-SEC-024 — capabilities.env.allow names a credential-shaped variable

Fires when: an entry in capabilities.env.allow reaches every WASM component and its name is credential-shaped. Either rule makes a name credential-shaped:

  1. It contains api_key, token, secret or password, in any case.
  2. One of its segments, split on every character that is not a letter or a digit, is KEY, KEYS, PASS, PASSWD, PASSPHRASE, CREDENTIAL, CREDENTIALS, CREDS, DSN, AUTH, PAT or COOKIE, in any case.

A name the credential backstop strips never reaches a component and is refused with E-CAP-016 instead. The warning prints once per distinct entry, in declaration order, on stderr, from mur run (including mur run --explain-scope) and from mur doctor in identical words.

The rule is measured against a fixed list of 26 names that carry credentials and 16 that do not:

Result Names
Caught: 22 of 26 Rule 1 alone catches 6 of 26, including DATABASE_PASSWORD, SLACK_BOT_TOKEN and STRIPE_API_KEY. Rule 2 adds 16, including PRIVATE_KEY, SSH_KEY, CREDENTIALS, SENTRY_DSN, SMTP_PASS and GH_PAT
Missed DATABASE_URL, REDIS_URL, DB_PWD, CONNECTION_STRING
Warned about but carry no credential TOKENIZERS_PARALLELISM, CACHE_KEY_PREFIX
Never warned about PATH, PWD, AUTHOR_NAME, KEYBOARD_LAYOUT, PASSENGER_COUNT and the other ordinary names on the list

A name the rule misses is still delivered to every WASM component, so review a connection-string entry such as DATABASE_URL yourself.

A credential-shaped name:

[capsule-runtime] warning[W-SEC-024]: capabilities.env.allow names 'DATABASE_PASSWORD', a credential-shaped variable the credential backstop does not drop — every WASM guest this capsule runs observes the host's value. murmur does not broker this secret and cannot withdraw it: for as long as the capsule runs, the capsule holds it (https://docs.murmur.nexus/reference/diagnostics/#w-sec-024)

A credential-shaped name on a capsule whose resolved lifecycle.after_task is sleep gets one further sentence, because the capsule holds the value after the task that launched it is gone:

[capsule-runtime] warning[W-SEC-024]: capabilities.env.allow names 'DATABASE_PASSWORD', a credential-shaped variable the credential backstop does not drop — every WASM guest this capsule runs observes the host's value. murmur does not broker this secret and cannot withdraw it, and lifecycle.after_task: sleep keeps this capsule alive past the task that launched it, so it holds that value with nothing left waiting on it (https://docs.murmur.nexus/reference/diagnostics/#w-sec-024)

Why it matters: capabilities.env.allow is the one grant whose value murmur never issues, sees or revokes — an operator names a host variable, and the runtime passes it through. The line is how you tell which of your capsules holds which secret.

What the runtime does about it: nothing is refused and no exit code changes. The grant is as effective as it would be unwarned. The judgment is made from the variable's name alone: the line reads the same on a host that has the variable set and one that does not, and no value ever appears in it.

What to do: decide whether this capsule should hold that secret for its whole life, and point the variable at the narrowest credential that does the job — murmur cannot rotate or withdraw it for you.

A capsule that holds an operator-granted secret for its whole life is a legitimate shape, which is why this is a warning and not a refusal — including with after_task: sleep. lifecycle.after_task is not a trigger on its own: with no credential-shaped name declared there is nothing being held.

W-SEC-025 — capabilities.network.allow names a gateway's upstream

Fires when: an entry in the capsule-wide capabilities.network.allow matches any artifact's gateway.endpoint, or an entry in an artifact's own capabilities.network.allow matches that artifact's gateway.endpoint. Once per matching entry, naming the entry and the artifact, on stderr, from mur run (including mur run --explain-scope) and from mur doctor.

[capsule-runtime] warning[W-SEC-025]: capabilities.network.allow entry 'https://api.anthropic.com' names the gateway.endpoint host of artifact 'murmur/driver-anthropic'; the gateway does not use it — the runtime reaches that upstream itself — so the entry only grants tools, subprocesses and artifacts direct reach to that host without the key (https://docs.murmur.nexus/reference/diagnostics/#w-sec-025)

For an entry on an artifact's own capabilities:, the warning begins artifact '<name>' capabilities.network.allow entry.

The runtime reaches the upstream for the artifact and attaches the key there, so the gateway works with or without the entry. The entry still grants direct access to that host, without the key. Remove it unless something other than the gateway needs that host.

W-SEC-026 — retired

Nothing raises this code and nothing reuses it. spend.machine_tokens_per_day counts a transport: process capsule's turns from the token counts the harness reports and its driver relays. Two codes cover what happens around that:

Code Covers
E-RUN-038 A spend ceiling set against a driver that reports no usage
W-SEC-031 A harness billing against something other than the subscription

W-SEC-027 — a gateway key is read only at launch

Fires when: an artifact's gateway.api_key can only be read at launch. That is either a literal value in murmur.yaml, or a ${NAME} that credentials.<NAME> does not hold and the launching shell's environment supplies. It fires once per such key, naming the artifact, on stderr, from mur run (including mur run --explain-scope) and from mur doctor.

[capsule-runtime] warning[W-SEC-027]: artifact 'murmur/driver-anthropic' gateway.api_key: ${ANTHROPIC_API_KEY} is read from the environment variable ANTHROPIC_API_KEY, because credentials.ANTHROPIC_API_KEY is not set in the global config, so the key is read once at launch and this capsule cannot pick up a rotated key until it is restarted; store the key with `mur config set -g credentials.ANTHROPIC_API_KEY <key>` to have it re-read (https://docs.murmur.nexus/reference/diagnostics/#w-sec-027)

For a literal, the warning names artifact '<name>' gateway.api_key is written literally in murmur.yaml and never prints the value.

Why it matters: a key stored with mur config set -g credentials.<NAME> reaches every running capsule on its next keyed request. This capsule keeps the key it launched with, so revoking that key breaks it until it restarts — for the driver's key, with E-RUN-027.

What the runtime does about it: nothing is refused. Exporting the key is how CI runs are keyed.

What to do: store the key with mur config set -g credentials.<NAME> <key> and write gateway.api_key: ${NAME} on the artifact's entry. A ${NAME} that neither the config nor the environment holds does not warn: the launch is refused with E-MAN-003.

W-SEC-028 — a file holding a secret is readable by other accounts

Fires when: a path under ~/.murmur that is expected to be owner-only has a mode wider than that. It fires from two places, on stderr:

Source Fires for How often
mur run The config file a capsule reads a gateway.api_key from, as credentials.<NAME>, when its mode grants any group or other permission Once per launch. A key from the environment or a literal never fires it, and neither does a re-read while the capsule runs
mur doctor Every owner-only entry in the ~/.murmur modes table wider than its mode, and every directory wider than 0700 or file wider than 0600 beneath one — under formations/, down to each member's directory only Once per path
[capsule-runtime] warning[W-SEC-028]: the credential credentials.ANTHROPIC_API_KEY is read from /home/alice/.murmur/config.yaml, which is mode 0644 and readable by other accounts on this host; run `chmod 600 /home/alice/.murmur/config.yaml` (https://docs.murmur.nexus/reference/diagnostics/#w-sec-028)
[capsule-runtime] warning[W-SEC-028]: /home/alice/.murmur/deploy_keys/dep_x/id_ed25519 holds SSH private keys and is mode 0644, which other accounts on this host can read; run `chmod 600 /home/alice/.murmur/deploy_keys/dep_x/id_ed25519` (https://docs.murmur.nexus/reference/diagnostics/#w-sec-028)

Why it matters: any account on the host can read the provider key, deploy key or record the path holds.

What the runtime does about it: nothing is refused and no mode is changed. murmur sets these modes on every write, so a wide path was loosened by hand, restored from a backup, or written by another tool.

What to do: run the chmod the warning names. Running mur config set -g again also rewrites config.yaml at 0600.

W-SEC-029 — a compiler driver could not be asked where its toolchain lives

Fires when: the capsule's declared containment floor is sealed, capabilities.shell.allow names a compiler driver that W-SEC-012 checks, and running that driver to ask where a helper lives fails before the driver answers. It fires once per shell.allow entry, on stderr, at staging and from mur doctor.

Failure Reported as
The driver could not be started — for example Permission denied, Text file busy, or a resource error The operating system's error message
The driver was killed by a signal before it answered terminated by signal N
[capsule-runtime] warning[W-SEC-029]: capabilities.shell.allow grants the compiler driver 'cc', but running /home/alice/bin/cc -print-prog-name=<helper> failed (Permission denied (os error 13)), so W-SEC-012 was not evaluated for its helpers [cc1, cc1plus, as, ld, collect2] and they may have no Execute grant under the 'sealed' composed root; check that /home/alice/bin/cc can be executed by this user and re-run `mur doctor` (https://docs.murmur.nexus/reference/diagnostics/#w-sec-029)

The bracketed list names the helpers that were not checked. Helpers checked before the failure are reported with W-SEC-012 as usual.

Why it matters: the helpers in the bracketed list were not checked, so the capsule may have a toolchain it cannot run under sealed even though no W-SEC-012 names it.

What the runtime does about it: nothing is refused, and the driver is not run again.

What to do: make the driver named in the warning runnable by the user launching the capsule, then run mur doctor. A Text file busy or resource error that clears on its own is gone on the next mur doctor.

W-SEC-030 — an unmetered credential gateway

Fires when: an artifact entry declares gateway: and is not the transport: http driver inference.driver.artifact names. Once per such gateway, on stderr, from mur run (including mur run --explain-scope) and from mur doctor. Never for the configured driver.

[capsule-runtime] warning[W-SEC-030]: artifact 'local/tool-web-search' reaches api.tavily.com through the credential gateway, unmetered — murmur neither counts nor limits what its calls spend, and inference.max_session_tokens and spend.machine_tokens_per_day do not cover it (https://docs.murmur.nexus/reference/diagnostics/#w-sec-030)

Why it matters: only the configured driver's calls count toward the spend ceilings. This artifact's calls count toward neither, so a capsule that stops at its token ceiling can keep spending on this upstream.

What the runtime does about it: nothing is refused. session_start.gateways records the gateway with metered: false.

What to do: bound the artifact's spend with the upstream's own controls, such as a key with its own quota.

W-SEC-031 — a harness that is not on a subscription

[capsule-runtime] warning[W-SEC-031]: the harness session reports auth 'api-key', not 'subscription' — this run's spend may be billed to an API key rather than to the subscription this transport exists for (https://docs.murmur.nexus/reference/diagnostics/#w-sec-031)

Why it matters: a transport: process capsule runs on a harness the operator is already signed in to, and the point of that is spend on that plan. A harness billing another way spends money against a key instead. The ceilings still count it — inference.max_session_tokens and spend.machine_tokens_per_day are enforced against whatever the harness reports, however it is billing — but the bill arrives somewhere the operator did not choose.

What the runtime does about it: nothing is refused. The value is whatever the harness reported through its process driver, compared only against subscription. The trace carries the same text as a harness_warning event, beside the harness_session event that reported it.

What to do: sign the harness in on the plan the run should spend from, or bound the key's spend with the provider's own controls.

W-SEC-032 — a public door off loopback

Fires when: a capsule's A2A door is exposed off this host and its manifest declares no network.authentication. Once per invocation, from:

Command Exposure
mur run --bind <ADDR> ADDR is off loopback
mur doctor --bind <ADDR> ADDR is off loopback
mur deploy run Always: it publishes the port at the VM's public address

Loopback is an IPv4 address in 127.0.0.0/8, ::1 or localhost; 0.0.0.0, :: and every other address or hostname are off it. A capsule with no inference: block serves no door and never warns. mur run and mur doctor print the same bytes for the same manifest and address.

warning[W-SEC-032]: the door is bound to 0.0.0.0 and network.authentication is not declared — any caller that reaches it can call SendMessage, SendStreamingMessage, murmur.WatchSession, GetTask, ListTasks, CancelTask, SubscribeToTask, murmur.StopSession, and the agent card publishes to any A2A client the session id, tools [bash], shell: true, network: true and planes [files] (https://docs.murmur.nexus/reference/diagnostics/#w-sec-032)

Why it matters: a public door answers every caller that reaches the port. Anyone on the network can start tasks, read and watch every task, and cancel every task at once with murmur.StopSession, and the agent card tells them the session id, the tools, and whether the capsule holds shell and network grants. The methods and the disclosure in the line are read from the manifest.

What the runtime does about it: nothing is refused. The capsule binds and serves as it would on loopback.

What to do: declare network.authentication, hand callers the tokens mur run prints, and put a TLS terminator in front of the door, which speaks plain HTTP. Or bind to loopback.

W-SEC-033 — the runtime or records directory lies inside the workdir

Fires when: ~/.murmur/sessions, where every session's runtime directory is created, or the session's records directory, lies inside the work folder:

From When
mur run Once per launch, when the declared containment is advisory
mur doctor For <manifest-dir>/workdir, whatever the declared containment
[capsule-runtime] warning[W-SEC-033]: the runtime directory /home/dev/.murmur/sessions and the records directory /home/dev/.murmur/records/home/dev/<session-id> lie inside the workdir /home/dev, so this capsule's commands can change the files the runtime owns — task.md, MURMUR.md, plans and installed tools — and the records it keeps about the session — its trace, logs, eval scores and delegation outcome; run with a --workdir that does not contain ~/.murmur (https://docs.murmur.nexus/reference/diagnostics/#w-sec-033)

Why it matters: the capsule's commands can rewrite the task, MURMUR.md, a submitted plan before it runs, an installed native tool before its next call, or the trace and eval scores every later reader trusts.

What the runtime does about it: the launch runs, records achieved_containment: advisory, and reports read_only_to_commands: false for the directory that lies inside. A scoped or sealed floor is refused with E-CAP-003 instead.

What to do: run with a --workdir that does not contain ~/.murmur.