iii-error-handling

$npx mdskill add iii-hq/iii/iii-error-handling

Diagnose and resolve iii engine and SDK errors across multiple languages.

  • Interpret error codes for function not found, invocation failures, and RBAC denials.
  • Depends on iii engine logs, SDK exception surfaces, and worker connectivity.
  • Branches on exact code strings rather than matching message text patterns.
  • Determines retryability based on error type and engine protocol state.

SKILL.md

.github/skills/iii-error-handlingView on GitHub ↗
---
name: iii-error-handling
description: >-
  Handle iii engine and SDK errors across Node, Python, Rust, and browser
  workers. Use when interpreting error codes, retryability, RBAC denial,
  timeouts, handler failures, or SDK-specific exception surfaces.
---

# Error Handling

iii has two broad error classes: SDK/local errors and engine/remote invocation errors. Agents should branch on the error code instead of matching only message strings.

## Error Codes

Branch on exact `code` strings, but keep engine wire codes separate from SDK-local codes.

| Code | Emitted by | Meaning | Typical handling |
| --- | --- | --- | --- |
| `function_not_found` | Engine and SDK local dispatch | No registered function is available under that ID | Check function ID, worker install/startup, discovery, and trigger type hints |
| `invocation_error` | Engine invocation/router path | Engine failed to route, remember, or complete the invocation | Inspect engine logs, protocol state, and worker connectivity |
| `invocation_stopped` | Engine invocation handler | Invocation was cancelled or stopped by the engine/runtime | Treat as failed work; decide whether caller should retry |
| `FORBIDDEN` | RBAC / worker-gated engine functions | RBAC denied the action | Do not retry blindly; inspect policy, auth context, and allowed functions |
| `timeout` | Engine/worker wire error when a worker reports lowercase timeout | Invocation exceeded a timeout reported through the wire protocol | Treat as timeout, but do not assume every SDK maps it to a timeout subclass |
| `function_not_invokable` | SDK local dispatch | Registration exists but cannot be invoked as a normal local function | Inspect registration/invocation type |
| `invocation_failed` | SDK worker handler wrappers | Local worker handler, HTTP-invoked function wrapper, or SDK-side handler path failed | Inspect handler logs, stacktrace, and payload validation |
| `TIMEOUT` | Node/Python SDK caller timeout | Client waited longer than `trigger()` timeout | Increase timeout only if the workload is expected to run long; otherwise optimize or enqueue |

## Handler vs Engine Errors

- Handler errors originate in user function code, SDK local dispatch, or HTTP-invoked endpoints.
- Engine errors originate in routing, invocation state, RBAC, protocol handling, or worker-reported wire errors.
- Queue retries only apply to enqueued work. Synchronous failures are returned directly to the caller.
- Void dispatch does not return handler results, so use logs/observability for failures.

## Retryability

- Retry transient `timeout`, `TIMEOUT`, transport, or worker reconnect failures only when the operation is idempotent.
- Do not retry `FORBIDDEN` without changing auth/policy.
- Do not retry `function_not_found` by calling the same ID repeatedly; discover functions or install/start the missing worker.
- For reliable background work, use `TriggerAction.Enqueue({ queue })` and queue retry/DLQ policy.

## SDK Surfaces

### Node

```typescript
import { IIIInvocationError } from 'iii-sdk'

try {
  await iii.trigger({ function_id: 'orders::charge', payload })
} catch (error) {
  if (error instanceof IIIInvocationError && error.code === 'FORBIDDEN') {
    throw new Error('Policy denied orders::charge')
  }
  throw error
}
```

### Python

```python
from iii import IIIForbiddenError, IIIInvocationError, IIITimeoutError

try:
    result = iii.trigger({"function_id": "orders::charge", "payload": payload})
except IIIForbiddenError:
    raise RuntimeError("Policy denied orders::charge")
except IIITimeoutError:
    raise RuntimeError("orders::charge timed out")
except IIIInvocationError as exc:
    if exc.code == "timeout":
        raise RuntimeError("orders::charge timed out")
    raise RuntimeError(f"{exc.code}: {exc.message}")
```

### Rust

```rust
match iii.trigger(request).await {
    Ok(value) => value,
    Err(iii_sdk::IIIError::Timeout) => {
        return Err("orders::charge timed out".into());
    }
    Err(iii_sdk::IIIError::Remote { code, message, .. }) if code == "FORBIDDEN" => {
        return Err(format!("policy denied: {message}").into());
    }
    Err(err) => return Err(err.into()),
}
```

### Browser

Browser trigger calls reject with JavaScript errors. Preserve the engine-provided code/message when present and show policy failures as permission errors in UI.

## Pattern Boundaries

- For invocation modes and enqueue decisions, prefer `iii-core-primitives`.
- For SDK-specific exception classes and syntax, prefer `iii-sdk-reference`.
- For workflow-level retry and DLQ design, prefer `iii-architecture-patterns`.
- For RBAC policy design and logs/traces around worker failures, use the matching worker docs under `engine/src/workers/**/skills`.

## When to Use

- Use this skill when the task mentions iii errors, exception handling, failed invocations, timeouts, forbidden calls, retry behavior, or SDK error classes.

## Boundaries

- Do not retry non-idempotent work automatically unless it is enqueued under queue policy.
- Do not treat RBAC denial as a missing worker.
- Do not generate removed service APIs or adapter-extension APIs.

More from iii-hq/iii