> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nika.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Error codes

> The typed error model — namespaces, categories, and the 28 registered v0.1 codes. Generated from the spec registry.

Every error Nika emits is a **typed structure** with a stable code, a
category, and a `transient` flag the retry machinery reads. The format is
`NIKA-<NAMESPACE>-<NNN>` — an optional sub-namespace self-documents builtin
errors (`NIKA-BUILTIN-WAIT-001`). The full grammar:

```
^NIKA-[A-Z]{2,9}(-[A-Z][A-Z0-9_]{1,15})?-[0-9]{3}$
```

<Info>
  **Canonical source:** [`spec/05-errors.md`](https://github.com/supernovae-st/nika-spec/blob/main/spec/05-errors.md)
  owns the taxonomy — engines *derive* from it, never the reverse. The
  machine-readable registry lives in the spec's
  [`canon.yaml`](https://github.com/supernovae-st/nika-spec/blob/main/canon.yaml)
  and is also served as JSON at
  [`nika.sh/errors/catalog.json`](https://nika.sh/errors/catalog.json).
  The tables below are generated from that registry.
</Info>

## The error shape

```json theme={"system"}
{
  "code": "NIKA-INFER-001",
  "category": "provider_error",
  "message": "Anthropic API returned 503 service unavailable",
  "transient": true,
  "details": { "provider": "anthropic", "status_code": 503 },
  "task_id": "research",
  "attempt": 2
}
```

`transient: true` means a retry might succeed — `retry:` only fires on
transient errors (unless `on_codes:` widens it). `on_error:` catches the
final error either way, and `on_error.on_codes` routes recovery by exact
code.

## Categories

| Category           | Meaning                                                           |
| ------------------ | ----------------------------------------------------------------- |
| `parse_error`      | Workflow YAML is malformed or invalid                             |
| `validation_error` | Workflow violates a spec rule (cycle · unknown field · …)         |
| `variable_error`   | Reference to undefined variable or invalid path                   |
| `provider_error`   | LLM provider returned an error                                    |
| `network_error`    | Network failure (DNS · TCP · TLS · timeout)                       |
| `tool_error`       | Builtin or MCP tool returned an error                             |
| `process_error`    | exec: subprocess failure (non-zero exit · spawn)                  |
| `budget_error`     | An agent: loop budget exhausted (max\_turns · max\_tokens\_total) |
| `security_error`   | SSRF · blocklist · capability denied                              |
| `timeout_error`    | Task or step exceeded its timeout                                 |
| `cancelled`        | Workflow or task cancelled                                        |
| `internal_error`   | Engine bug · unexpected state                                     |

## The 28 registered codes (v0.1 normative floor)

A conformant engine emits exactly these codes for these failures. Engines
may add codes within a namespace's `001-099` range — never repurpose one.

| Code                    | Failure                                                                                                                                                | Category           | `transient`     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ | --------------- |
| `NIKA-PARSE-001`        | the YAML itself does not parse (syntax error)                                                                                                          | `parse_error`      | false           |
| `NIKA-PARSE-002`        | missing envelope field (nika: / workflow: / non-empty tasks:)                                                                                          | `validation_error` | false           |
| `NIKA-PARSE-003`        | nika: version marker is not exactly v1                                                                                                                 | `parse_error`      | false           |
| `NIKA-PARSE-004`        | workflow: id violates ^\[a-z]\[a-z0-9-]\*\$                                                                                                            | `validation_error` | false           |
| `NIKA-PARSE-005`        | unknown field — strict mode rejects anything outside the closed v1 set                                                                                 | `validation_error` | false           |
| `NIKA-PARSE-006`        | task id violates ^\[a-z]\[a-z0-9\_]\*\$ (snake\_case · CEL-safe · no hyphens)                                                                          | `validation_error` | false           |
| `NIKA-PARSE-007`        | duplicate task id within the workflow                                                                                                                  | `validation_error` | false           |
| `NIKA-PARSE-008`        | task declares no verb — exactly one of infer/exec/invoke/agent required                                                                                | `validation_error` | false           |
| `NIKA-PARSE-009`        | task declares multiple verbs — exactly one required                                                                                                    | `validation_error` | false           |
| `NIKA-PARSE-010`        | timeout: violates the quoted Go-duration contract (positive · max 24h · descending units)                                                              | `validation_error` | false           |
| `NIKA-PARSE-011`        | retry: block violates the spec shape                                                                                                                   | `validation_error` | false           |
| `NIKA-PARSE-012`        | on\_error: block violates the spec shape (fields mutually exclusive)                                                                                   | `validation_error` | false           |
| `NIKA-PARSE-013`        | with:/output: binding uses a reserved name (output · status · error · started\_at · ended\_at · duration\_ms)                                          | `validation_error` | false           |
| `NIKA-PARSE-014`        | secrets: entry is not a store reference — inline literals forbidden                                                                                    | `validation_error` | false           |
| `NIKA-PARSE-015`        | typed vars: declaration malformed (type in string/number/integer/boolean/array/object)                                                                 | `validation_error` | false           |
| `NIKA-PARSE-017`        | duplicate mapping key — no silent last-wins                                                                                                            | `validation_error` | false           |
| `NIKA-PARSE-018`        | missing required field in a verb body (infer.prompt · exec.command · invoke.tool)                                                                      | `validation_error` | false           |
| `NIKA-PARSE-019`        | generic structural validation — wrong YAML shape for a field                                                                                           | `validation_error` | false           |
| `NIKA-DAG-001`          | cycle in depends\_on (incl. self-dependency)                                                                                                           | `validation_error` | false           |
| `NIKA-DAG-002`          | depends\_on references an undeclared task                                                                                                              | `validation_error` | false           |
| `NIKA-DAG-003`          | a tasks.X reference with no declared edge                                                                                                              | `validation_error` | false           |
| `NIKA-DAG-004`          | on\_error.recover references a task downstream of the declaring task (await would deadlock)                                                            | `validation_error` | false           |
| `NIKA-VAR-001`          | unresolved reference (unknown namespace entry · undeclared env/vars key)                                                                               | `variable_error`   | false           |
| `NIKA-VAR-002`          | binding cardinality — a jq binding emitted zero or multiple values                                                                                     | `variable_error`   | false           |
| `NIKA-VAR-003`          | provably-invalid path into a declared schema (static walk)                                                                                             | `validation_error` | false           |
| `NIKA-VAR-004`          | jq runtime error while evaluating a binding                                                                                                            | `variable_error`   | false           |
| `NIKA-VAR-005`          | static expression violation — outside cel-subset/0.1 · chained relation · unknown function · non-boolean when: root · jq compile error                 | `validation_error` | false           |
| `NIKA-VAR-006`          | expression type error at evaluation — cross-type compare · non-boolean when: value · for\_each over a non-array                                        | `variable_error`   | false           |
| `NIKA-VAR-007`          | bytes value substituted into a string position                                                                                                         | `variable_error`   | false           |
| `NIKA-VAR-008`          | unclosed \$\{\{ opener                                                                                                                                 | `validation_error` | false           |
| `NIKA-VAR-009`          | typed outputs value did not match its declared type: at run end (the output half of the callable contract)                                             | `validation_error` | false           |
| `NIKA-INFER-001`        | provider call failed (HTTP error · provider refusal)                                                                                                   | `provider_error`   | engine-assessed |
| `NIKA-INFER-002`        | structured output failed schema validation (after any engine-internal retries)                                                                         | `validation_error` | false           |
| `NIKA-EXEC-001`         | non-zero exit code (default capture modes)                                                                                                             | `process_error`    | false           |
| `NIKA-EXEC-002`         | spawn failure (command not found · permission)                                                                                                         | `process_error`    | false           |
| `NIKA-INVOKE-001`       | unknown tool (unresolvable nika:/mcp: id)                                                                                                              | `validation_error` | false           |
| `NIKA-INVOKE-002`       | tool args failed the tool's schema                                                                                                                     | `validation_error` | false           |
| `NIKA-AGENT-001`        | max\_turns exhausted before completion                                                                                                                 | `budget_error`     | false           |
| `NIKA-AGENT-002`        | max\_tokens\_total exhausted before completion                                                                                                         | `budget_error`     | false           |
| `NIKA-MCP-001`          | MCP server not configured / not reachable at call time                                                                                                 | `tool_error`       | engine-assessed |
| `NIKA-MCP-002`          | MCP tool call failed (transport · tool-side error)                                                                                                     | `tool_error`       | engine-assessed |
| `NIKA-SEC-001`          | exec: blocklist hit                                                                                                                                    | `security_error`   | false           |
| `NIKA-SEC-002`          | agent tool call outside the tools: whitelist                                                                                                           | `security_error`   | false           |
| `NIKA-SEC-003`          | run-recursion bound — nested-run depth exceeded OR self-launching workflow                                                                             | `security_error`   | false           |
| `NIKA-SEC-004`          | effect outside the declared permits: capability boundary (fs/net/exec/tool)                                                                            | `security_error`   | false           |
| `NIKA-SEC-005`          | SSRF block — a nika:fetch/nika:notify URL resolves to a loopback/private/link-local/metadata target (always-on engine floor · independent of permits:) | `security_error`   | false           |
| `NIKA-TIMEOUT-001`      | task (or for\_each iteration) exceeded timeout:                                                                                                        | `timeout_error`    | false           |
| `NIKA-CANCEL-001`       | task cancelled (workflow failure gate · user cancellation)                                                                                             | `cancelled`        | false           |
| `NIKA-BUILTIN-001`      | builtin invoke violates its statically-checkable arg contract (e.g. nika:fetch without url: · nika:jq arg shape)                                       | `validation_error` | false           |
| `NIKA-BUILTIN-DONE-001` | nika:done invoked outside an agent: loop                                                                                                               | `validation_error` | false           |

## Namespaces

| Namespace       | Scope                                           | Range   |
| --------------- | ----------------------------------------------- | ------- |
| `NIKA-AGENT`    | agent: verb errors                              | 001-099 |
| `NIKA-BUILTIN`  | Builtin tool errors · per-builtin sub-namespace | 001-099 |
| `NIKA-CANCEL`   | Task or workflow cancellation                   | 001-099 |
| `NIKA-DAG`      | DAG topology · cycles · invalid deps            | 001-099 |
| `NIKA-EXEC`     | exec: verb errors                               | 001-099 |
| `NIKA-IMPL`     | Engine internal errors                          | 001-099 |
| `NIKA-INFER`    | infer: verb errors                              | 001-099 |
| `NIKA-INVOKE`   | invoke: verb errors                             | 001-099 |
| `NIKA-MCP`      | MCP client errors                               | 001-099 |
| `NIKA-PARSE`    | YAML parse + envelope validation                | 001-099 |
| `NIKA-PROVIDER` | Provider adapter errors                         | 001-099 |
| `NIKA-SEC`      | Security policy violations (SSRF · blocklist)   | 001-099 |
| `NIKA-TIMEOUT`  | Task or step timeouts                           | 001-099 |
| `NIKA-VAR`      | Variable resolution failures                    | 001-099 |

Builtin errors use a per-builtin sub-namespace — each builtin owns its own
`001-099` (`NIKA-BUILTIN-FETCH-001` is the fetch tool's network/extraction
failure). Underscore-named builtins encode naturally:
`NIKA-BUILTIN-JSON_MERGE_PATCH-001`.

## Routing on codes

```yaml theme={"system"}
# Retry ONLY on the fetch network error · fixed 30s backoff
retry:
  max_attempts: 5
  backoff_strategy: fixed
  backoff_ms: 30000
  on_codes: [NIKA-BUILTIN-FETCH-001]

# Recover ONLY on timeout · any other code still fails
on_error:
  on_codes: [NIKA-TIMEOUT-001]
  recover: { stale: true, items: [] }
```

`on_error.skip` preserves the original error at `tasks.X.error` — a
downstream task can branch on `${{ tasks.X.error.code }}` while the status
reads `skipped`.

<Card title="The full error model" icon="book" href="https://github.com/supernovae-st/nika-spec/blob/main/spec/05-errors.md">
  Retry policies, backoff strategies, recovery semantics, and the
  gate-based failure propagation rules — spec/05-errors.md.
</Card>
