Skip to content

Iterion DSL — Validation Diagnostics

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).

Compilation Diagnostics

CodeSeverityDescriptionCauseFix
C001errorUnknown node referenceAn edge references a node that is not declaredDeclare the node or fix the name typo
C002errorUnknown schema referenceA node's input: or output: references an undeclared schemaDeclare the schema or fix the name
C003errorUnknown prompt referenceA node's system: or user: references an undeclared promptDeclare the prompt or fix the name
C004errorBad template referenceA {{...}} template expression is malformedUse {{vars.X}}, {{input.X}}, {{outputs.node.field}}, {{artifacts.X}}, {{attachments.X}}, {{loop.name.iteration}}, or {{run.id}}
C005errorDuplicate loop definitionMultiple edges share a loop name but disagree on max_iterationsUse the same max_iterations value or use different loop names
C006errorNo workflow foundThe file has no workflow declarationAdd a workflow name: block
C007errorMultiple workflowsMore than one workflow block foundV1 supports one workflow per file — remove extras
C008errorMissing entry nodeThe entry: node name doesn't match any declared nodeFix the entry name or declare the node
C018error/warningMissing model/backend or LLM interaction requirementsAgents/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.
C024errorDuplicate MCP serverA mcp_server name is declared more than onceUse unique names for each MCP server
C025errorInvalid MCP server configMCP 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
C030warningCodex backend discouragedA 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.
C039errorCompute node has no expressionsA compute node was declared without any expr: key: "<expression>" entriesAdd at least one expression mapping an output schema field to an expression — or remove the node
C040errorExpression failed to parseAn expression in a compute node or in a quoted when "..." clause isn't validCheck operators, parentheses, namespace prefixes (vars / input / outputs / artifacts / loop / run), and built-in calls (length, concat, unique, contains, join, if)
C041errorDuplicate node idTwo declarations share the same node name across agents/judges/routers/humans/tools/computesRename one — node ids are a single global namespace
C042errorReserved node nameA user node is named done or fail (those are reserved terminal targets)Pick a different node name
C044errorInvalid sandbox modeA node or workflow's sandbox: mode is outside the accepted set ("", none, auto, inline); or inline mode is missing an image/build or sets bothSet 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:.
C045errorSandbox auto without configReserved 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 presentNo 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).
C046errorInvalid budget costbudget.max_cost_usd is negative, NaN, or infinityUse a non-negative finite USD amount, or omit the field to disable the cost cap.

Validation Diagnostics

CodeSeverityDescriptionCauseFix
C009errorSession at convergence pointA node with await: (or multiple incoming sources) uses session: inherit or session: forkChange to session: fresh or session: artifacts_only
C010errorMultiple unconditional edgesA non-router node has more than one unconditional outgoing edgeKeep only one default edge, or use a router for fan-out
C011errorAmbiguous conditionsSame condition field appears twice with same polarity from the same sourceRemove the duplicate edge or use different conditions
C012errorMissing fallbackA node has conditional edges but no unconditional fallback and conditions aren't exhaustiveAdd when not X to complement when X, or add an unconditional edge
C013errorCondition field not booleanA when clause references a field that isn't bool in the source output schemaChange the schema field to bool
C014errorCondition field not foundA when clause references a field that doesn't exist in the source output schemaAdd the field to the schema or fix the field name
C015errorElse without conditional siblingAn else edge has no when sibling from the same source — a fallback with nothing to fall back from is just an unconditional edge wearing a misleading keywordAdd the conditional sibling(s), or use a plain src -> dst edge
C016errorUnreachable nodeA declared node cannot be reached from the workflow's entry: nodeAdd edges to reach the node, or remove the unused declaration
C017errorHistory ref not in loop{{outputs.node.history}} is used but the referenced node is not part of any declared loopAdd a loop declaration (as loop_name(N)) to the edge cycle, or remove the .history reference
C019errorUndeclared cycleA cycle (back-edge) exists without any loop declaration on its edgesAdd as loop_name(N) to the back-edge to bound the cycle
C020errorRound-robin too few edgesA round_robin router has fewer than 2 unconditional outgoing edgesAdd at least 2 outgoing edges for alternation
C021errorLLM router too few edgesAn llm router has fewer than 2 outgoing edgesAdd at least 2 outgoing edges for the LLM to choose from
C022errorLLM router edge has conditionAn edge from an llm router has a when clauseRemove the when clause — LLM routers select targets directly
C023errorLLM-only property on non-LLM routerProperties model, backend, system, user, multi, or reasoning_effort are set on a router that isn't mode: llmRemove these properties or change the mode to llm
C026errorInvalid loop iterationsA loop's max_iterations is less than 1Set max_iterations to at least 1
C027errorInvalid reasoning effortreasoning_effort has a value other than low, medium, high, xhigh, max, ultracodeUse one of the six valid values
C028errorDuplicate with-mapping keyThe same with key appears on multiple non-conditional edges to the same targetUse unique keys, or make edges conditional/convergent
C029errorUnknown outputs node referenceA {{outputs.<node>...}} template targets a node not declared anywhere in the fileDeclare the node or fix the typo
C031erroroutputs ref field not in output schema{{outputs.<node>.<field>}} references a field absent from that node's output: schemaReference an existing field, or add the field to the schema
C032warningoutputs ref on schemaless node{{outputs.<node>.<field>}} targets a node that has no output: schema, so the field cannot be verifiedAdd an output: schema to the source node, or drop the field access
C033errorUndeclared variable{{vars.X}} (or vars.X inside an expression) targets a variable not declared in the file-level or workflow-level vars: blockDeclare the variable, or fix the name
C034errorinput ref field not in input schema{{input.<field>}} references a field absent from the consuming node's input: schemaReference an existing field, or add it to the schema
C035errorUnknown artifact{{artifacts.X}} targets an artifact never produced via publish:Add publish: <name> on a prior node, or fix the artifact name
C036errorReference to non-reachable node{{outputs.<node>...}} targets a node not reachable from the entry before the consumerReorder the graph or wire an edge so the producer runs first
C037warningNode max_tokens exceeds workflow budgetA node-level max_tokens is greater than the workflow's budget.max_tokensLower the node cap, or raise the workflow budget
C038errorUnsupported MCP auth typemcp_server.auth.type is something other than oauth2 (the only wired type)Drop the auth: block, or change type to oauth2
C043errorInvalid compaction valuescompaction.threshold is outside (0, 1] or compaction.preserve_recent is < 1Use a fraction like 0.85 for threshold and an integer >= 1 for preserve_recent; omit either to inherit the default
C047warningMemory enabled on unsupported backendA 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 proceedsDrop the memory: block, or switch the node to backend: claw (or another backend that has memory wired).
C048errorMemory missing scopeA 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).
C049warningArtifact labels without publishA node sets artifact_labels: but has no publish:, so the labels have no artifact to attach toAdd publish: <name>, or remove the artifact_labels: (judge nodes never publish, so their labels are dropped at compile time)
C050errorDuplicate attachmentAn attachment name is declared more than once across file-level and workflow-level attachments: blocksRename the duplicate, or merge the definitions
C051errorAttachment / var name collisionAn attachment name collides with a declared vars: entryRename one of them — attachments and vars share a single template namespace
C052errorInvalid attachment MIMEAn accept_mime: entry is not in type/subtype form (e.g. image/png, application/pdf)Use type/subtype MIME values, optionally with * subtype wildcards
C053errorUnknown attachment reference{{attachments.X}} references an attachment that is not declared in a file-level or workflow-level attachments: blockDeclare the attachment, or fix the name
C054errorUnknown attachment sub-field{{attachments.<name>.<subfield>}} uses a sub-field the runtime does not exposeDrop the sub-field or pick a supported one (path, url, mime, size, sha256)
C055errorBad prompt includeA prompt {{include "..."}} marker could not be resolved: the file is missing, is a directory, exceeds the 256 KiB cap, uses an absolute path, or escapes the .bot directoryPoint the include at an existing file inside the .bot's directory subtree, below the size cap
C060errorPlaywright MCP server requires browser-capable sandbox imageAn MCP server with the Playwright transport is configured but the workflow's sandbox image is not browser-capableUse 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
C070errorPreset references unknown variableA presets: entry sets a key that does not match any name in vars:Add the variable to vars:, or remove/rename the preset key
C071errorPreset value type mismatchA presets: value's type (string/int/bool/list) does not match the declared vars: typeCast the value to the declared type, or change the var's type
C072errorDuplicate preset nameThe same preset name appears more than once in the presets: blockRename or merge the duplicate preset
C080warningUnknown capabilityA capabilities: entry isn't in the built-in registry (currently: board.read, board.create, board.move, board.assign, board.label, board.close, board.comment, watch.subscribe, watch.unsubscribe)Either fix the typo or accept the warning — unknown caps still propagate to the executor (the registry is open for extension)
C081errorMalformed capabilityA 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
C082warningBoard capability inside sandboxA 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 serverNo action required if the iterion HTTP server is reachable from the sandbox; otherwise drop the capability or disable the sandbox for that node
C083warningUnknown cursor referenceAn agent/judge cursors: setting references a cursor name not declared at workflow scopeDeclare it with cursor <name>: or drop the setting — see docs/cursors.md
C084errorInvalid cursor valueA cursor invocation value is not in the enum, falls outside [0, 1], or doesn't match any band. ${VAR} values defer to runtimeUse a declared enum value or a numeric in range; for env-driven values, ensure the substituted result is valid
C085errorMalformed cursor declarationA 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]
C086errorDuplicate cursor nameThe same cursor <name>: declaration appears twice in one sourceRename one of them, or merge their values: / bands: entries
C087warningUnknown providerA provider: chain token is outside the known provider setFix the provider name, or accept the warning for a newly-added provider
C088warningProvider chain ignoredA 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
C089warningUltracode model gatereasoning_effort: ultracode on a model other than claude-opus-4-8 — the orchestration half is 4.8-only, so it degrades to plain xhighUse model: "claude-opus-4-8" for full ultracode, or accept the xhigh degrade
C090errorDuplicate secretA secret name is declared more than once in the secrets: blockRename or merge the duplicate
C091errorSecret / var name collisionA secret name collides with a declared vars: entryRename one — secrets and vars share a template namespace
C092errorInvalid secret hostA secret's egress host scoping (Layer 2 hosts:) is ill-formedUse valid host entries (hostnames / domains)
C093errorUnknown secret reference{{secrets.X}} references a secret not declared in the secrets: blockDeclare the secret, or fix the name
C094errorMalformed file secretAn as: file secret declaration is malformedProvide a valid value:/env: and file-mount form
C095errorUnsupported secret sub-field{{secrets.X.<subfield>}} uses a sub-field the runtime does not exposeDrop the sub-field, or reference {{secrets.X}} directly
C097errorUnbounded loop without fuelAn as name(unbounded) loop has no fuel ceiling (neither a per-loop unbounded <N> nor a workflow budget.max_iterations) — the "no silent infinity" invariantAdd a per-loop fuel (as name(unbounded 200)) or a workflow budget.max_iterations
C098warningUnbounded loop without exitAn unbounded loop's body has no edge leaving the loop — only fuel/liveness can stop itAdd a when-exit (convergence condition) so the loop terminates by its own logic
C100errorReview without worktreeinteraction: review without worktree: auto — there is nothing to mergeAdd worktree: auto, or drop the review interaction
C101warningReview URL unknown refreview_url references an output node that does not existFix the node reference, or remove review_url
C102errorInvalid compress valuecompress: is not one of on, off, ultraUse on, off, or ultra
C103errorInvalid policyA Verified Action tool node's policy: is not one of required, recover, best_effort (ADR-044)Use a known policy value
C104errorRecovery without postconditionA tool node configures recovery: (or policy: recover) without a postcondition: — the deterministic truth oracle that makes adaptive recovery safeAdd a postcondition:, or drop the recovery
C105errorRecovery on a gateRecovery 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
C106warningRecovery without recover policyrecovery: bounds are present but policy: is not recover — dead configSet policy: recover, or remove the recovery bounds
C107warningExpression operand type mismatchA 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
C108warningwhen-expression not booleanA 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 intentUse a comparison such as when "count > 0". Bare bool, string[], and string values ride the documented truthy idiom and are not flagged
C109errorVar default type mismatchA vars: entry's default literal type doesn't match its declared type (e.g. count: int = "x")Fix the default to match the type. intfloat widening is allowed (ratio: float = 5); json and string[] accept loose literals and are never flagged
C110errorInvalid permissionpermission: is not one of off, ask, denyUse off, ask, or deny
C111warningPermission rules without gateallow/ask/deny rule lists are declared but the resolved permission mode is ""/off, so they never applySet permission: ask or deny, or remove the rule lists
C112warningTool-node permission inertpermission: 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
C113errorfan_out_each without overA fan_out_each router has no over: array sourceAdd over: "{{...array...}}" to the router
C114errorfan_out_each property on non-fan_out_eachover/as/key/depends_on set on a router that isn't mode: fan_out_eachRemove the property, or change the router mode
C115errorfan_out_each edge countA fan_out_each router must have exactly one outgoing template edgeKeep a single template edge from the router
C116errorUse references unknown groupA use ... as statement references a group that is not declaredDeclare the group, or fix the name
C117errorUse param mismatchA use provides an unknown param, or omits a declared oneMatch the group's declared params exactly
C118errorforeach conflicts with loopAn edge combines as foreach with as <loop> (mutually exclusive)Use one iteration form per edge
C119errorsubbot without sourceA subbot node has no source: child .botAdd source: <path>.bot to the subbot
C120warningIndex on scalarA subscript [...] is applied to a statically-scalar value (string/bool/int/float), which is not indexableIndex an array/map, or drop the subscript
C121errorEnum literal never matchesA 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 typoUse a declared enum value, or fix the field's enum: set. json fields and unresolved refs are never flagged
C122errorInvalid node timeoutAn agent/judge timeout: is not a positive Go duration (after ${VAR:-default} expansion)Use a positive Go duration string, e.g. timeout: "20m" or "1200s"
C123errorMultiple else edgesA source node has more than one else edge — two fallbacks firing on the same miss is the C010 ambiguity under a new nameKeep exactly one else per source
C124errorElse alongside unconditionalA source node has both an else edge and a bare unconditional edge — two competing defaultselse IS the fallback: remove the bare unconditional edge, or drop the else keyword
C125errorVar enum on non-string typeA vars: entry declares an [enum: ...] constraint on a non-string type (e.g. count: int [enum: "a"]) — enums constrain string values onlyDeclare the var as string, or drop the constraint
C126errorVar default not in enumAn enum-constrained var's default is not one of the declared values (e.g. mode: string [enum: "a", "b"] = "c"). Launch-provided values get the same check at run start: the engine rejects any --var / HTTP / dispatcher / preset value outside the enum setUse one of the enum values as the default, or extend the list
C127warningDuplicate var enum valueThe same value appears more than once in a var's [enum: ...] list — the duplicate is dropped (first occurrence kept, order preserved)Remove the duplicate value
C128warningSandbox opt-outThe workflow (or a node) explicitly declares sandbox: none — every tool and shell command runs directly on the host/runner with its credentials and filesystem, while sandboxing is the engine defaultRemove the sandbox: none block to run sandboxed; keep the opt-out only if the flow genuinely needs the host environment
C129errorfile field outside a human pauseA schema used as the output: of a node that never pauses for an operator declares a file-typed field — any non-human node, or a human node with interaction: llm (auto-answered by a model) or interaction: review (output is the engine-built verdict). Only an operator upload at a real pause produces oneMove the file field to a human node's output: schema with interaction: human (or llm_or_human, which can escalate to a pause), or use string if the node computes a path itself
C130errorReserved answer keyA human node's output: schema declares a key the engine writes on resume (_attachments, which carries the gate's ad-hoc operator uploads) — the operator's answer would be silently replacedRename the field. The reservation applies only to human gates; elsewhere the name is an ordinary field
C131errorInvalid auto_memory valueauto_memory: (workflow or agent/judge node) is not one of on, off — a typo would silently read as "inherit", i.e. offUse on or off, or drop the field to inherit
C132warningauto_memory on an unsupported backendAn agent/judge node explicitly declares auto_memory: on but its backend: does not consume MEMORY.md (claude_code, claw and pi do)Switch to one of those backends, or drop the auto_memory: field
C170errorInvalid memory visibilitymemory: visibility: has an unknown valueUse a known visibility (bot/project/cross_project/user/org/global)
C171errorMemory visibility conflictmemory: visibility: is combined with the legacy project_root:Use visibility: alone — drop the legacy project_root:
C172warningMalformed provider stepA provider: chain element of the provider:model form has an empty provider or model partProvide both parts, e.g. anthropic:claude-sonnet-4-6
C173errorMalformed fallback routeA fallbacks: route declares neither backend, model nor provider (it would re-issue the identical failing call), duplicates another route's name, or switches backend without pinning its own model: (model specs are not portable across backends)Give the route a distinct target; a route that changes backend: must also set model:
C174warningCommand ignoredA per-node command: CLI-binary override is set on a backend that ignores it (claw/codex) — only claude_code honors itSwitch to backend: "claude_code", or drop the command:
C175warningUnknown fallback triggerA fallbacks: route's on: list names a category the runtime does not classifyUse usage_window, auth, unavailable, transient_exhausted or any
C176errorUnsafe fallback crossingA fallbacks: route would silently change what the node can DO: it runs on a backend that cannot enforce the node's permission: gate, or it crosses the claw⇄CLI boundary on a node with an empty tools: list (empty means NO tools on claw and the FULL native toolset on a CLI backend)Route to a gate-enforcing backend (claude_code/claw/pi), or declare an explicit tools: list
C177warningFallback capability driftA fallbacks: route runs on a backend that silently ignores one of the node's settings (e.g. reasoning_effort:)Accept the degradation, or pin the route to a backend that honours the setting
C190warningSupervisor watches non-agentA supervisor watches: a node id that isn't an agent nodeWatch an agent node, or fix the node id
C191warningMalformed supervisorA supervisor declaration is malformed (e.g. a bad cooldown duration)Use a valid Go duration for cooldown
C192errorDuplicate supervisorThe same supervisor <name>: is declared twiceRename or merge
C193warningUnknown supervisor promptA supervisor's system: references an undeclared promptDeclare the prompt, or fix the name
C194errorInvalid resource capacityA resources.<name> capacity is ≤ 0Use a capacity ≥ 1
C195errorUnknown resource in needsA needs: references a resource not declared in the resources: blockDeclare the resource, or fix the name
C196errorEvent node without nameAn emit/wait node has no event: name (ADR-051)Add event: "<name>"
C197errorWait without timeoutA wait node has no (or an invalid/non-positive) timeout: — the mandatory bound, the "no silent infinity" invariant for eventsAdd timeout: "30s" (a positive Go duration)
C198warningDangling eventA 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)
C199warningInvalid skill referenceA 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 warnFix the name; quote kebab-case names (skills: ["changelog-writer"])
C240errorAsync interaction on human nodeinteraction: async is set on a human node — async questions are posted by agent/judge nodes; a human node IS the blocking questionMove interaction: async to the asking agent/judge and use an await_answers node as the sync point
C241errorawait_answers without timeoutAn await_answers node has no (or an invalid/non-positive) timeout: — the mandatory bound, the "no silent infinity" invariantAdd timeout: "30m" (a positive Go duration)
C242warningawait_answers with dead fromAn await_answers from: names a node that is missing or not an interaction: async agent/judge — no async question can originate there, so the await can only ever time outFix the from: reference, or set interaction: async on the referenced node

Note on C103C106 (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 as C103 is now C121 — a C103 always means invalid policy. See docs/adr/044-adaptive-recovery-for-deterministic-action-nodes.md.

Historical code-reuse note: earlier releases reused C030 for two cases. C029 was introduced for the validator-side unknown outputs node reference error; C030 now only flags the compile-time Codex backend discouraged warning. If an older log shows C030 on an outputs.<unknown> reference, treat it as the modern C029.

Bundle Consistency Diagnostics (manifest ↔ workflow)

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.

CodeSeverityDescriptionCauseFix
C200warningdispatch_vars key not a workflow varA manifest.dispatch_vars key names no variable in the workflow vars: blockDeclare the var in main.bot, or fix/remove the manifest key — an undeclared key is silently dropped at dispatch time
C201warningcontext_vars key not a workflow varAn invocations[].context_vars key names no workflow varSame as C200
C202warningschedule.default_vars key not a workflow varAn invocations[].schedule.default_vars key names no workflow varSame as C200
C203warninglaunch_vars key not a workflow varA forge.webhook.launch_vars key names no workflow varSame as C200
C204warningargs_var not a workflow varAn invocations[].args_var names no workflow var, so the trigger's free-text payload is droppedDeclare the var, or fix the name
C210warningforge secret not declaredThe forge secret the bot expects (forge.secret, default forge_token) has no matching declaration in the main.bot secrets: blockDeclare 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)
C211warningforge secret not a file mountThe forge secret is declared but not as: file — managed forge tokens are bound as a file mountSet as: file on the secret declaration
C220warningmanifest capability granted by no nodeA manifest.capabilities entry is granted by no workflow-level or node-level capabilities: listAdd it to a node's capabilities:, or drop it from the manifest (it is documentation-only otherwise)
C221warningfrontmatter capabilities override manifestThe main.bot ## --- frontmatter declares capabilities: that differ from and silently override the manifest'sKeep one source of truth — drop the frontmatter list or align the two
C230errorper-bot memory name mismatchA 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) launchesMake all three identical
C231warningskill has no name:A skills/*.md file has no name: frontmatter, so it is undiscoverable by name once mirrored into .claude/skills/Add name: <kebab-case-id> to the skill frontmatter
C232warningskill has no description:A skills/*.md file has no description: frontmatter, so the router (Nexie) has no signal for when to select itAdd a description: saying what the skill is for and when it applies
C233warningskill description: too terseA skill description: is present but too short to route on (e.g. "Security stuff")Describe what the skill does and the situation it applies to. Routability only — no phrasing template is imposed
C234warningduplicate skill nameTwo skills/*.md files declare the same name:, so one silently clobbers the other when mirroredGive each skill a unique name:

The skill-authoring checks (C231–C234) guard routability — that a skill can be discovered and chosen by the router — not prose style. They impose no phrasing template and are all warnings, so a skill gap never fails validation.

Quick Troubleshooting

"I get C019 (undeclared cycle)" Every back-edge (edge that creates a cycle) needs as loop_name(N). Example:

iter
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.