All diagnostic codes emitted during compilation (ir.Compile) and validation (ir.Validate), plus the bundle-consistency codes (C2xx) that iterion validate reports for a packaged bot. Diagnostics are either errors (block execution) or warnings (informational).
| Code | Severity | Description | Cause | Fix |
|---|---|---|---|---|
| C001 | error | Unknown node reference | An edge references a node that is not declared | Declare the node or fix the name typo |
| C002 | error | Unknown schema reference | A node’s input: or output: references an undeclared schema |
Declare the schema or fix the name |
| C003 | error | Unknown prompt reference | A node’s system: or user: references an undeclared prompt |
Declare the prompt or fix the name |
| C004 | error | Bad template reference | A template expression is malformed | Use, ,, ,, , or |
|
| C005 | error | Duplicate loop definition | Multiple edges share a loop name but disagree on max_iterations |
Use the same max_iterations value or use different loop names |
| C006 | error | No workflow found | The file has no workflow declaration |
Add a workflow name: block |
| C007 | error | Multiple workflows | More than one workflow block found |
V1 supports one workflow per file — remove extras |
| C008 | error | Missing entry node | The entry: node name doesn’t match any declared node |
Fix the entry name or declare the node |
| C018 | error/warning | Missing model/backend or LLM interaction requirements | Agents/judges without model: or backend: are errors only when no default supervisor model and no auto-detectable runtime credentials are available. mode: llm routers without either value produce a warning and use the built-in runtime default. Human nodes using interaction: llm or interaction: llm_or_human must set model: or interaction_model: and must declare output:. |
Add model: "...", backend: "...", or configure detectable credentials/defaults for agents/judges; set explicit model/backend for LLM routers when you do not want runtime defaulting; for LLM-backed human nodes add the interaction model and output schema. |
| C024 | error | Duplicate MCP server | A mcp_server name is declared more than once |
Use unique names for each MCP server |
| C025 | error | Invalid MCP server config | MCP server misconfigured (e.g., stdio without command, http/sse without url) | Match properties to transport type: stdio needs command; http and sse need url and must not set command or args |
| C030 | warning | Codex backend discouraged | A node uses backend: "codex" |
Codex is still supported but has limitations (cannot configure tool set, fills its own context window, weaker integration). Prefer backend: "claude_code" for tool-using agents or claw (default) with an OpenAI model (model: "openai/gpt-5.4-mini") for judges/reviewers. |
| C039 | error | Compute node has no expressions | A compute node was declared without any expr: key: "<expression>" entries |
Add at least one expression mapping an output schema field to an expression — or remove the node |
| C040 | error | Expression failed to parse | An expression in a compute node or in a quoted when "..." clause isn’t valid |
Check operators, parentheses, namespace prefixes (vars / input / outputs / artifacts / loop / run), and built-in calls (length, concat, unique, contains, join, if) |
| C041 | error | Duplicate node id | Two declarations share the same node name across agents/judges/routers/humans/tools/computes | Rename one — node ids are a single global namespace |
| C042 | error | Reserved node name | A user node is named done or fail (those are reserved terminal targets) |
Pick a different node name |
| C044 | error | Invalid sandbox mode | A node or workflow’s sandbox: mode is outside the accepted set ("", none, auto, inline); or inline mode is missing an image/build or sets both |
Set sandbox: to auto, none, inline, or omit it. Block-form sandbox config with image:, build:, env:, mounts:, or network: compiles as inline mode unless mode: is specified; inline requires exactly one of image: or build:. |
| C045 | error | Sandbox auto without config | Reserved diagnostic code; not currently emitted by compile/validation. Normal CLI/runtime auto mode supplies a default iterion-sandbox-slim:<version> fallback when no .devcontainer/devcontainer.json is present |
No compile-time action. If an embedder disables the default image and runtime reports a missing devcontainer, add .devcontainer/devcontainer.json, provide a default image, or use inline sandbox: with image:/build: (see docs/sandbox.md). |
| C046 | error | Invalid budget cost | budget.max_cost_usd is negative, NaN, or infinity |
Use a non-negative finite USD amount, or omit the field to disable the cost cap. |
| Code | Severity | Description | Cause | Fix |
|---|---|---|---|---|
| C009 | error | Session at convergence point | A node with await: (or multiple incoming sources) uses session: inherit or session: fork |
Change to session: fresh or session: artifacts_only |
| C010 | error | Multiple unconditional edges | A non-router node has more than one unconditional outgoing edge | Keep only one default edge, or use a router for fan-out |
| C011 | error | Ambiguous conditions | Same condition field appears twice with same polarity from the same source | Remove the duplicate edge or use different conditions |
| C012 | error | Missing fallback | A node has conditional edges but no unconditional fallback and conditions aren’t exhaustive | Add when not X to complement when X, or add an unconditional edge |
| C013 | error | Condition field not boolean | A when clause references a field that isn’t bool in the source output schema |
Change the schema field to bool |
| C014 | error | Condition field not found | A when clause references a field that doesn’t exist in the source output schema |
Add the field to the schema or fix the field name |
| C016 | error | Unreachable node | A declared node cannot be reached from the workflow’s entry: node |
Add edges to reach the node, or remove the unused declaration |
| C017 | error | History ref not in loop | `` is used but the referenced node is not part of any declared loop | Add a loop declaration (as loop_name(N)) to the edge cycle, or remove the .history reference |
| C019 | error | Undeclared cycle | A cycle (back-edge) exists without any loop declaration on its edges | Add as loop_name(N) to the back-edge to bound the cycle |
| C020 | error | Round-robin too few edges | A round_robin router has fewer than 2 unconditional outgoing edges |
Add at least 2 outgoing edges for alternation |
| C021 | error | LLM router too few edges | An llm router has fewer than 2 outgoing edges |
Add at least 2 outgoing edges for the LLM to choose from |
| C022 | error | LLM router edge has condition | An edge from an llm router has a when clause |
Remove the when clause — LLM routers select targets directly |
| C023 | error | LLM-only property on non-LLM router | Properties model, backend, system, user, multi, or reasoning_effort are set on a router that isn’t mode: llm |
Remove these properties or change the mode to llm |
| C026 | error | Invalid loop iterations | A loop’s max_iterations is less than 1 |
Set max_iterations to at least 1 |
| C027 | error | Invalid reasoning effort | reasoning_effort has a value other than low, medium, high, xhigh, max |
Use one of the five valid values |
| C028 | error | Duplicate with-mapping key | The same with key appears on multiple non-conditional edges to the same target |
Use unique keys, or make edges conditional/convergent |
| C029 | error | Unknown outputs node reference | A `` template targets a node not declared anywhere in the file | Declare the node or fix the typo |
| C031 | error | outputs ref field not in output schema | `` references a field absent from that node’s output: schema |
Reference an existing field, or add the field to the schema |
| C032 | warning | outputs ref on schemaless node | `` targets a node that has no output: schema, so the field cannot be verified |
Add an output: schema to the source node, or drop the field access |
| C033 | error | Undeclared variable | `` (or vars.X inside an expression) targets a variable not declared in the file-level or workflow-level vars: block |
Declare the variable, or fix the name |
| C034 | error | input ref field not in input schema | `` references a field absent from the consuming node’s input: schema |
Reference an existing field, or add it to the schema |
| C035 | error | Unknown artifact | `` targets an artifact never produced via publish: |
Add publish: <name> on a prior node, or fix the artifact name |
| C036 | error | Reference to non-reachable node | `` targets a node not reachable from the entry before the consumer | Reorder the graph or wire an edge so the producer runs first |
| C037 | warning | Node max_tokens exceeds workflow budget | A node-level max_tokens is greater than the workflow’s budget.max_tokens |
Lower the node cap, or raise the workflow budget |
| C038 | error | Unsupported MCP auth type | mcp_server.auth.type is something other than oauth2 (the only wired type) |
Drop the auth: block, or change type to oauth2 |
| C043 | error | Invalid compaction values | compaction.threshold is outside (0, 1] or compaction.preserve_recent is < 1 |
Use a fraction like 0.85 for threshold and an integer >= 1 for preserve_recent; omit either to inherit the default |
| C047 | warning | Memory enabled on unsupported backend | A node sets memory: enabled: true but its resolved backend doesn’t read the field — only the claw backend wires memory tools today. The check is informational; the run still proceeds |
Drop the memory: block, or switch the node to backend: claw (or another backend that has memory wired). |
| C048 | error | Memory missing scope | A node sets memory: enabled: true without a non-empty scope: <name> — the runtime needs the scope to locate ~/.iterion/projects/<key>/memory/<scope>/ |
Add scope: <name> to the node’s memory: block (the scope becomes the directory the memory_read / memory_write / memory_list tools operate against). |
| C049 | warning | Artifact labels without publish | A node sets artifact_labels: but has no publish:, so the labels have no artifact to attach to |
Add publish: <name>, or remove the artifact_labels: (judge nodes never publish, so their labels are dropped at compile time) |
| C050 | error | Duplicate attachment | An attachment name is declared more than once across file-level and workflow-level attachments: blocks |
Rename the duplicate, or merge the definitions |
| C051 | error | Attachment / var name collision | An attachment name collides with a declared vars: entry |
Rename one of them — attachments and vars share a single template namespace |
| C052 | error | Invalid attachment MIME | An accept_mime: entry is not in type/subtype form (e.g. image/png, application/pdf) |
Use type/subtype MIME values, optionally with * subtype wildcards |
| C053 | error | Unknown attachment reference | `` references an attachment that is not declared in a file-level or workflow-level attachments: block |
Declare the attachment, or fix the name |
| C054 | error | Unknown attachment sub-field | `` uses a sub-field the runtime does not expose | Drop the sub-field or pick a supported one (path, url, mime, size, sha256) |
| C060 | error | Playwright MCP server requires browser-capable sandbox image | An MCP server with the Playwright transport is configured but the workflow’s sandbox image is not browser-capable | Use ghcr.io/socialgouv/iterion-sandbox-browser (or another browser-capable image whose name matches the validator predicate, such as one containing sandbox-browser or sandbox-full-browser), or remove the Playwright MCP server |
| C070 | error | Preset references unknown variable | A presets: entry sets a key that does not match any name in vars: |
Add the variable to vars:, or remove/rename the preset key |
| C071 | error | Preset value type mismatch | A presets: value’s type (string/int/bool/list) does not match the declared vars: type |
Cast the value to the declared type, or change the var’s type |
| C072 | error | Duplicate preset name | The same preset name appears more than once in the presets: block |
Rename or merge the duplicate preset |
| C080 | warning | Unknown capability | A capabilities: entry isn’t in the built-in registry (currently: board.read, board.create, board.move, board.assign, board.label, board.close) |
Either fix the typo or accept the warning — unknown caps still propagate to the executor (the registry is open for extension) |
| C081 | error | Malformed capability | A capabilities: entry doesn’t match the shape domain or domain.action (lowercase letters/digits/underscores) |
Use the lowercase domain.action form, e.g. board.create |
| C082 | warning | Board capability inside sandbox | A node grants a board.* capability while running under a sandbox — the stdio __mcp-board transport is unavailable, the runtime falls back to the HTTP transport on the iterion server |
No action required if the iterion HTTP server is reachable from the sandbox; otherwise drop the capability or disable the sandbox for that node |
| C083 | warning | Unknown cursor reference | An agent/judge cursors: setting references a cursor name not declared at workflow scope |
Declare it with cursor <name>: or drop the setting — see docs/cursors.md |
| C084 | error | Invalid cursor value | A cursor invocation value is not in the enum, falls outside [0, 1], or doesn’t match any band. ${VAR} values defer to runtime |
Use a declared enum value or a numeric in range; for env-driven values, ensure the substituted result is valid |
| C085 | error | Malformed cursor declaration | A cursor <name>: block declares neither values: nor bands:, declares both, has overlapping bands, or has a range outside [0, 1] |
Pick exactly one form (enum or numeric); ensure bands cover disjoint sub-ranges of [0, 1] |
| C086 | error | Duplicate cursor name | The same cursor <name>: declaration appears twice in one source |
Rename one of them, or merge their values: / bands: entries |
| C087 | warning | Unknown provider | A provider: chain token is outside the known provider set |
Fix the provider name, or accept the warning for a newly-added provider |
| C088 | warning | Provider chain ignored | A multi-element provider: chain is set on a backend that ignores the hint (claw/codex) |
Drop the extra chain elements, or switch to a backend that honours provider hints |
| C089 | warning | Ultracode model gate | reasoning_effort: ultracode on a model other than claude-opus-4-8 — the orchestration half is 4.8-only, so it degrades to plain xhigh |
Use model: "claude-opus-4-8" for full ultracode, or accept the xhigh degrade |
| C090 | error | Duplicate secret | A secret name is declared more than once in the secrets: block |
Rename or merge the duplicate |
| C091 | error | Secret / var name collision | A secret name collides with a declared vars: entry |
Rename one — secrets and vars share a template namespace |
| C092 | error | Invalid secret host | A secret’s egress host scoping (Layer 2 hosts:) is ill-formed |
Use valid host entries (hostnames / domains) |
| C093 | error | Unknown secret reference | `` references a secret not declared in the secrets: block |
Declare the secret, or fix the name |
| C094 | error | Malformed file secret | An as: file secret declaration is malformed |
Provide a valid value:/env: and file-mount form |
| C095 | error | Unsupported secret sub-field | uses a sub-field the runtime does not expose | Drop the sub-field, or reference directly |
|
| C097 | error | Unbounded loop without fuel | An as name(unbounded) loop has no fuel ceiling (neither a per-loop unbounded <N> nor a workflow budget.max_iterations) — the “no silent infinity” invariant |
Add a per-loop fuel (as name(unbounded 200)) or a workflow budget.max_iterations |
| C098 | warning | Unbounded loop without exit | An unbounded loop’s body has no edge leaving the loop — only fuel/liveness can stop it |
Add a when-exit (convergence condition) so the loop terminates by its own logic |
| C100 | error | Review without worktree | interaction: review without worktree: auto — there is nothing to merge |
Add worktree: auto, or drop the review interaction |
| C101 | warning | Review URL unknown ref | review_url references an output node that does not exist |
Fix the node reference, or remove review_url |
| C102 | error | Invalid compress value | compress: is not one of on, off, ultra |
Use on, off, or ultra |
| C103 | error | Invalid policy | A Verified Action tool node’s policy: is not one of required, recover, best_effort (ADR-044) |
Use a known policy value |
| C104 | error | Recovery without postcondition | A tool node configures recovery: (or policy: recover) without a postcondition: — the deterministic truth oracle that makes adaptive recovery safe |
Add a postcondition:, or drop the recovery |
| C105 | error | Recovery on a gate | Recovery rungs are attached to a GATE (a node where recipe == postcondition) |
Remove the recovery: block — gates stay deterministic; never attach LLM recovery to a gate |
| C106 | warning | Recovery without recover policy | recovery: bounds are present but policy: is not recover — dead config |
Set policy: recover, or remove the recovery bounds |
| C107 | warning | Expression operand type mismatch | A comparison inside a compute or quoted when "..." expression has statically-known operands of incompatible type classes (e.g. string[] == int, count < "x") |
Compare compatible types. Inference is conservative: json (= any) fields, vars, and unresolved refs bail to “no opinion” and are never flagged |
| C108 | warning | when-expression not boolean | A quoted when "<expr>" is a bare numeric value (e.g. when "count") rather than a boolean — int/float coerce to truthy, which is rarely the author’s intent |
Use a comparison such as when "count > 0". Bare bool, string[], and string values ride the documented truthy idiom and are not flagged |
| C109 | error | Var default type mismatch | A vars: entry’s default literal type doesn’t match its declared type (e.g. count: int = "x") |
Fix the default to match the type. int→float widening is allowed (ratio: float = 5); json and string[] accept loose literals and are never flagged |
| C110 | error | Invalid permission | permission: is not one of off, ask, deny |
Use off, ask, or deny |
| C111 | warning | Permission rules without gate | allow/ask/deny rule lists are declared but the resolved permission mode is ""/off, so they never apply |
Set permission: ask or deny, or remove the rule lists |
| C112 | warning | Tool-node permission inert | permission: on a tool node — parsed but not enforced (a tool node runs a fixed command, not an agent) |
Remove the permission: from the tool node; gate the agent nodes instead |
| C113 | error | fan_out_each without over | A fan_out_each router has no over: array source |
Add over: "" to the router |
| C114 | error | fan_out_each property on non-fan_out_each | over/as/key/depends_on set on a router that isn’t mode: fan_out_each |
Remove the property, or change the router mode |
| C115 | error | fan_out_each edge count | A fan_out_each router must have exactly one outgoing template edge |
Keep a single template edge from the router |
| C116 | error | Use references unknown group | A use ... as statement references a group that is not declared |
Declare the group, or fix the name |
| C117 | error | Use param mismatch | A use provides an unknown param, or omits a declared one |
Match the group’s declared params exactly |
| C118 | error | foreach conflicts with loop | An edge combines as foreach with as <loop> (mutually exclusive) |
Use one iteration form per edge |
| C119 | error | subbot without source | A subbot node has no source: child .bot |
Add source: <path>.bot to the subbot |
| C120 | warning | Index on scalar | A subscript [...] is applied to a statically-scalar value (string/bool/int/float), which is not indexable |
Index an array/map, or drop the subscript |
| C121 | error | Enum literal never matches | A when "field == 'literal'" / != comparison (or a compute expression) compares an enum-typed field against a literal that is not one of its enum: values — the comparison can never match, so it is almost always a typo |
Use a declared enum value, or fix the field’s enum: set. json fields and unresolved refs are never flagged |
| C170 | error | Invalid memory visibility | memory: visibility: has an unknown value |
Use a known visibility (bot/project/cross_project/user/org/global) |
| C171 | error | Memory visibility conflict | memory: visibility: is combined with the legacy project_root: |
Use visibility: alone — drop the legacy project_root: |
| C172 | warning | Malformed provider step | A provider: chain element of the provider:model form has an empty provider or model part |
Provide both parts, e.g. anthropic:claude-sonnet-4-6 |
| C190 | warning | Supervisor watches non-agent | A supervisor watches: a node id that isn’t an agent node |
Watch an agent node, or fix the node id |
| C191 | warning | Malformed supervisor | A supervisor declaration is malformed (e.g. a bad cooldown duration) |
Use a valid Go duration for cooldown |
| C192 | error | Duplicate supervisor | The same supervisor <name>: is declared twice |
Rename or merge |
| C193 | warning | Unknown supervisor prompt | A supervisor’s system: references an undeclared prompt |
Declare the prompt, or fix the name |
| C194 | error | Invalid resource capacity | A resources.<name> capacity is ≤ 0 |
Use a capacity ≥ 1 |
| C195 | error | Unknown resource in needs | A needs: references a resource not declared in the resources: block |
Declare the resource, or fix the name |
| C196 | error | Event node without name | An emit/wait node has no event: name (ADR-051) |
Add event: "<name>" |
| C197 | error | Wait without timeout | A wait node has no (or an invalid/non-positive) timeout: — the mandatory bound, the “no silent infinity” invariant for events |
Add timeout: "30s" (a positive Go duration) |
| C198 | warning | Dangling event | A wait awaits an event no emit produces (it can only ever time out), or an emit produces an event no wait consumes (dead event) |
Pair each wait with an emit of the same event name (a wait on an externally-sourced event is expected to warn until external-event support lands) |
| C199 | warning | Invalid skill reference | A skills: entry (on a node or the workflow) is not a valid skill name — a single path segment of letters/digits/./-/_, not starting with a dot (ADR-059). Existence in the library is resolved at run time, not here, so an unknown-but-well-formed name does not warn |
Fix the name; quote kebab-case names (skills: ["changelog-writer"]) |
Note on
C103–C106(Verified Actions, ADR-044): these four codes are the adaptive-recovery firewall on deterministic ACTION tool nodes. The enum-literal type check that earlier releases emitted asC103is nowC121— aC103always means invalid policy. See docs/adr/044-adaptive-recovery-for-deterministic-action-nodes.md.
Historical code-reuse note: earlier releases reused
C030for two cases.C029was introduced for the validator-side unknown outputs node reference error;C030now only flags the compile-time Codex backend discouraged warning. If an older log showsC030on anoutputs.<unknown>reference, treat it as the modernC029.
These C2xx diagnostics come from pkg/bundlelint, emitted when iterion validate
runs on a bundle (a .botz archive or a directory with manifest.yaml +
main.bot). They cross-check the manifest against the compiled workflow —
something neither the manifest parser (pkg/bundle) nor the DSL compiler
(pkg/dsl/ir) can do alone, because each only sees one side. They are reported
under a separate bundle_diagnostics list in --json output. All are warnings
except C230; warnings are surfaced but do not fail validation.
| Code | Severity | Description | Cause | Fix |
|---|---|---|---|---|
| C200 | warning | dispatch_vars key not a workflow var | A manifest.dispatch_vars key names no variable in the workflow vars: block |
Declare the var in main.bot, or fix/remove the manifest key — an undeclared key is silently dropped at dispatch time |
| C201 | warning | context_vars key not a workflow var | An invocations[].context_vars key names no workflow var |
Same as C200 |
| C202 | warning | schedule.default_vars key not a workflow var | An invocations[].schedule.default_vars key names no workflow var |
Same as C200 |
| C203 | warning | launch_vars key not a workflow var | A forge.webhook.launch_vars key names no workflow var |
Same as C200 |
| C204 | warning | args_var not a workflow var | An invocations[].args_var names no workflow var, so the trigger’s free-text payload is dropped |
Declare the var, or fix the name |
| C210 | warning | forge secret not declared | The forge secret the bot expects (forge.secret, default forge_token) has no matching declaration in the main.bot secrets: block |
Declare secrets: { <name>: { as: file, optional: true } }, or point forge.secret at an existing secret. Only checked when the bot is forge-triggerable (has forge.events or a kind: forge invocation) |
| C211 | warning | forge secret not a file mount | The forge secret is declared but not as: file — managed forge tokens are bound as a file mount |
Set as: file on the secret declaration |
| C220 | warning | manifest capability granted by no node | A manifest.capabilities entry is granted by no workflow-level or node-level capabilities: list |
Add it to a node’s capabilities:, or drop it from the manifest (it is documentation-only otherwise) |
| C221 | warning | frontmatter capabilities override manifest | The main.bot ## --- frontmatter declares capabilities: that differ from and silently override the manifest’s |
Keep one source of truth — drop the frontmatter list or align the two |
| C230 | error | per-bot memory name mismatch | A node uses memory: visibility: bot, but the manifest name, workflow name, and bundle dir name are not all identical — so the bot’s memory tree splits across CLI (workflow name) and dispatcher (bundle name) launches |
Make all three identical |
“I get C019 (undeclared cycle)”
Every back-edge (edge that creates a cycle) needs as loop_name(N). Example:
judge -> agent when not approved as retry(3) with { ... }
“I get C009 (session at convergence)”
Nodes that receive from multiple branches (via await: or fan-out) cannot use session: inherit or fork. Use session: fresh or session: artifacts_only.
“I get C012 (missing fallback)”
If you have when approved, you need either when not approved or an unconditional edge from the same source. Conditions must be exhaustive.
“I get C018 (missing model or backend)”
For agents and judges, add model: "..." or backend: "...", set ITERION_DEFAULT_SUPERVISOR_MODEL, or configure detectable backend credentials. For mode: llm routers, either set an explicit model:/backend: or accept the warning and runtime default. For human nodes with interaction: llm or interaction: llm_or_human, add model: or interaction_model: and declare an output: schema.