# Error handling

> Run and stream errors, exponential-backoff retries, and partial results after a failure.

Source: https://maincode.com/docs/agent-sdk-error-handling
Section: Agent SDK · Matilda documentation

---

## `MatildaAgentRunError`

Thrown on HTTP-level failures (non-2xx response from the server).

```ts
class MatildaAgentRunError extends Error {
  readonly status: number;       // HTTP status code
  readonly responseText: string; // Raw response body
}
```

## `MatildaAgentStreamError`

Thrown by `run()` when the stream emits an error event (unless `throwOnStreamError: false`). Carries the full `AgentRunResult` with whatever was collected before the error.

```ts
class MatildaAgentStreamError extends Error {
  readonly code: ChatErrorCode;
  readonly errors: ReadonlyArray<{ code: ChatErrorCode; message: string }>;
  readonly result: AgentRunResult;
}
```

```ts
import { MatildaAgentStreamError, MatildaAgentRunError } from '@maincode-ai/matilda-agent-sdk';

try {
  await runner.run(agent, input);
} catch (err) {
  if (err instanceof MatildaAgentStreamError) {
    console.log('Stream error code:', err.code);
    console.log('Partial output before error:', err.result.finalOutput);
    console.log('All errors:', err.errors);
  } else if (err instanceof MatildaAgentRunError) {
    console.log('HTTP error status:', err.status);
    console.log('Response body:', err.responseText);
  } else {
    throw err;
  }
}
```

## `SafetyReplaceError`

Thrown by `streamText()`, `runText()`, `streamObject()`, and `runObject()` when the server replaces the output via a safety filter. The `message` property contains the replacement text (or empty string), and `categories` lists the safety categories.

```ts
class SafetyReplaceError extends Error {
  readonly categories: string[];
}
```

## `MatildaObjectParseError`

Thrown by `streamObject()` / `runObject()` when the response cannot be parsed as JSON or fails zod validation — e.g. a truncated stream (see [Structured output](https://maincode.com/docs/agent-sdk-structured-output)). `raw` holds the full response text; `cause` is the underlying `JSON.parse` or zod error. Safety replacement does not surface here — it throws `SafetyReplaceError` first.

```ts
class MatildaObjectParseError extends Error {
  readonly raw: string;
  readonly cause: unknown;
}
```

## `AuthError`

Thrown by auth flows. The `code` field is an OAuth error code. The `retryable` field distinguishes transient failures from permanent ones.

```ts
class AuthError extends Error {
  readonly code: string;
  readonly retryable: boolean;
}
```

## Chat error codes (`ChatErrorCode`)

These codes are emitted via the `error` stream event and appear in `AgentRunResult.errors`:

| Code | Description |
| - | - |
| `internal_error` | Server-side failure. |
| `upstream_unavailable` | The AI model is not responding. |
| `rate_limited` | Too many requests. |
| `content_blocked` | Safety filter blocked the content. |
| `stream_aborted` | The stream was interrupted before completion. |
| `deadline_exceeded` | The response did not finish before the deadline. |
| `context_too_large` | The conversation is too long for the model. |
| `stalled` | No SSE events for the configured stall window. |
| `stream_expired` | The durable stream buffer expired (resume path only). |
| `unknown` | Unclassified error (old server without typed codes). |

## Stall watchdog

The streaming parser arms an idle-event watchdog. If no SSE event arrives for `stallTimeoutMs` milliseconds, the stream is considered dead and aborted with a `'stalled'` error.

- **Default:** `45_000` ms (`DEFAULT_AGENT_STALL_TIMEOUT_MS`)
- **Disable:** Pass `stallTimeoutMs: 0` in `AgentRunOptions` (not recommended)

## Retry behaviour

The SDK retries retryable errors within a single turn. Retries are controlled by `maxRetries` (default: `0` — no retries).

**Retryable errors:**

- HTTP 429 (rate limited), 408 (request timeout), 5xx (server errors)
- Network errors: `UND_ERR_SOCKET`, `UND_ERR_FETCH_ERROR`, `ETIMEDOUT`, `ECONNRESET`
- SSE stall watchdog (`stalled` error code)
- `TimeoutError` (but not `AbortError`)

**Retry delay:** Exponential backoff with jitter, capped at 30 seconds.

**Important:** Retries only happen when no text has been yielded to the consumer yet. Retrying past that point would replay text the caller already has.

When a retry occurs, a `turn.retrying` event is emitted:

```ts
for await (const event of runner.stream(agent, input, { maxRetries: 3 })) {
  if (event.type === 'turn.retrying') {
    console.log(`Retry ${event.attempt}/${event.maxRetries} in ${event.delayMs}ms: ${event.error.message}`);
  }
}
```

## Error handling example

```ts
import {
  Runner,
  MatildaAgentStreamError,
  MatildaAgentRunError,
  SafetyReplaceError,
} from '@maincode-ai/matilda-agent-sdk';

const runner = new Runner();

try {
  const text = await runner.runText(agent, 'Hello!');
  console.log(text);
} catch (err) {
  if (err instanceof MatildaAgentStreamError) {
    console.error(`Stream error: ${err.code} — ${err.message}`);
    console.log('Partial output:', err.result.finalOutput);
  } else if (err instanceof MatildaAgentRunError) {
    if (err.status === 401) {
      console.error('Session expired — re-authenticate.');
    } else if (err.status === 429) {
      console.error('Rate limited — slow down.');
    } else {
      console.error(`API error ${err.status}: ${err.responseText}`);
    }
  } else if (err instanceof SafetyReplaceError) {
    console.error(`Safety filter: ${err.categories.join(', ')}`);
  } else {
    console.error('Unexpected error:', err);
  }
}
```
