# Handling Errors, Timeouts, and Retries in Flue Agent Operations

> Learn how to handle errors timeouts and retries in Flue agent operations withastro/flue. Discover FlueError hierarchy AbortSignal and automatic retries for LLM calls.

- Repository: [Astro/flue](https://github.com/withastro/flue)
- Tags: how-to-guide
- Published: 2026-05-11

---

**Flue centralizes error handling through a structured `FlueError` hierarchy, converts timeouts into `AbortSignal` instances for sandbox operations, and automatically retries LLM calls when context windows overflow via its compaction subsystem.**

The `withastro/flue` SDK provides a robust error-handling framework designed for resilient agent operations. Every failure—whether from network timeouts, sandbox execution errors, or LLM context limits—propagates through a predictable type-safe pipeline. This architecture ensures that developers receive structured error metadata while end users see sanitized, actionable messages.

## The FlueError Hierarchy and Structured Error Metadata

All runtime errors in Flue inherit from the base `FlueError` class defined in **[`packages/sdk/src/errors.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/errors.ts)**. This centralization guarantees consistent error shapes across HTTP boundaries, sandbox calls, and model interactions.

The hierarchy separates fields by audience:
- **Caller-safe fields** (`message`, `details`) are always serialized to JSON responses
- **Developer-only fields** (`dev`) appear only when the server runs with `FLUE_MODE=local`
- **Structured metadata** (`type`, optional `meta`) enables programmatic error handling on the client side

```ts
// packages/sdk/src/errors.ts
export class FlueError extends Error {
  type: string;
  message: string;
  details?: string;
  dev?: string;
  meta?: Record<string, unknown>;
  // ...
}

export class FlueHttpError extends FlueError {
  statusCode: number;
  // HTTP-specific error rendering
}

```

When an error originates in the sandbox layer or session management, the SDK wraps it in the appropriate subclass (e.g., `FlueHttpError`) before sending it over the wire.

## Timeout Handling and Abort Signal Management

Flue converts numeric timeouts into standard Web **AbortSignal** instances to ensure compatibility with modern async patterns. In **[`packages/sdk/src/sandbox.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/sandbox.ts)**, the `exec`, `readFile`, and other sandbox methods translate the `timeout` option (specified in seconds) into an `AbortSignal.timeout` in milliseconds, then merge it with any user-provided signal.

```ts
// packages/sdk/src/sandbox.ts
const timeoutSignal =
  typeof opts?.timeout === 'number' ? AbortSignal.timeout(opts.timeout * 1000) : undefined;
const mergedSignal =
  opts?.signal && timeoutSignal 
    ? AbortSignal.any([opts.signal, timeoutSignal]) 
    : (opts?.signal ?? timeoutSignal);

```

If the merged signal aborts before the provider (E2B, Daytona, Modal) completes execution, the utility **`abortErrorFor(signal)`** in [`packages/sdk/src/abort.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/abort.ts) converts the raw `AbortSignal` into a well-typed `FlueError`. This error propagates up the call stack with a specific `type` identifier distinguishing timeouts from user cancellations.

## Error Propagation from Sandbox to HTTP Response

The **[`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts)** layer wraps each agent operation in a `try/catch` block that normalizes errors for the HTTP layer. When a sandbox call throws—whether from an abort signal or execution failure—the session intercepts it and attaches relevant context.

```ts
// packages/sdk/src/session.ts
catch (error) {
  const surfaced = signal?.aborted ? abortErrorFor(signal) : error;
  this.internalLog('error', `[flue:session] ${errorMessage}`, { error });
  // Converts to FlueHttpError subclass for the HTTP response
}

```

This normalization ensures the client always receives a JSON envelope with a predictable shape: `type`, `message`, `details`, and optionally `dev` when running locally. The HTTP router ([`handle-agent.ts`](https://github.com/withastro/flue/blob/main/handle-agent.ts)) relies on this structure to return proper status codes while sanitizing internal stack traces in production.

## Automatic Retry on Context Window Overflow

When a model response exceeds its context window, the LLM returns a special `isContextOverflow` error. Flue's **compaction subsystem** in **[`packages/sdk/src/compaction.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/compaction.ts)** detects this condition and automatically retries the operation after summarizing older messages.

The retry flow follows three steps:

1. **Detection** – After each model call, Flue checks for `isContextOverflow`
2. **Compaction** – `compactContext()` creates a summary prompt and updates the session's message list in-place
3. **Retry** – `syncHarnessMessagesSince(beforeRetry, 'retry')` re-synchronizes the harness state, marking the new turn with `source: 'retry'` as defined in **[`packages/sdk/src/types.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/types.ts)**

```ts
// packages/sdk/src/compaction.ts
if (willRetry) {
  await this.syncHarnessMessagesSince(beforeRetry, 'retry');
}

```

No explicit retry limit is enforced by default; the model determines when to stop generating. Developers can implement custom guards around session calls if bounded retries are required for specific use cases.

## Practical Implementation Examples

### Setting a Timeout on Sandbox Commands

Pass a `timeout` value (in seconds) to any sandbox execution method. The SDK handles conversion to milliseconds and signal management automatically.

```ts
import { createFlueFs } from '@flue/sdk';

async function runCommand(env) {
  const fs = createFlueFs(env);

  try {
    const result = await env.exec('curl https://example.com', {
      timeout: 5,  // 5 seconds
      cwd: '/tmp',
    });
    console.log('stdout:', result.stdout);
  } catch (err) {
    // Error will be an instance of FlueError
    if (err instanceof Error) {
      console.error('Command failed:', err.message);
    }
  }
}

```

### Handling AbortSignals in HTTP Handlers

Combine explicit timeouts with request-level `AbortSignal` instances for comprehensive cancellation support.

```ts
import { FlueError, MethodNotAllowedError } from '@flue/sdk';

export async function handler(req, env) {
  if (req.method !== 'POST') {
    throw new MethodNotAllowedError({ 
      method: req.method, 
      allowed: ['POST'] 
    });
  }

  try {
    const payload = await req.json();
    const execResult = await env.exec(payload.command, {
      timeout: payload.timeout,
      signal: req.signal,  // Request-level AbortSignal
    });
    return new Response(JSON.stringify(execResult), { status: 200 });
  } catch (e) {
    // Re-throw FlueError instances to let the framework handle serialization
    if (e instanceof FlueError) throw e;
    
    // Wrap unknown errors for safety
    throw new FlueError({
      type: 'internal',
      message: 'Unexpected failure',
      details: String(e),
      dev: '',
    });
  }
}

```

### Detecting Automatic Context Compaction

When Flue performs an automatic retry after overflow, it marks the assistant message with a specific source flag.

```ts
import { session } from '@flue/sdk';

await session.runPrompt({
  content: largeSourceCode,  // May cause overflow
});

if (session.lastMessage?.role === 'assistant' && 
    session.lastMessage.source === 'retry') {
  console.log('Context was compacted and operation retried automatically');
}

```

## Summary

- **Error Hierarchy**: All errors inherit from `FlueError` in [`packages/sdk/src/errors.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/errors.ts), ensuring consistent serialization with caller-safe (`message`, `details`) and developer-only (`dev`) fields.
- **Timeout Conversion**: Sandbox operations in [`packages/sdk/src/sandbox.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/sandbox.ts) convert numeric timeouts to `AbortSignal.timeout` instances and merge them with user signals using `AbortSignal.any`.
- **Abort Handling**: The `abortErrorFor` utility in [`abort.ts`](https://github.com/withastro/flue/blob/main/abort.ts) transforms aborted signals into typed `FlueError` instances that propagate through [`packages/sdk/src/session.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/session.ts).
- **Auto-Retry**: The compaction subsystem in [`packages/sdk/src/compaction.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/compaction.ts) detects `isContextOverflow`, summarizes older messages, and retries via `syncHarnessMessagesSince(beforeRetry, 'retry')`, marking retried turns with `source: 'retry'` from [`types.ts`](https://github.com/withastro/flue/blob/main/types.ts).

## Frequently Asked Questions

### How does Flue handle operation timeouts?

Flue accepts a `timeout` parameter (in seconds) on sandbox methods like `exec` or `readFile`. In [`packages/sdk/src/sandbox.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/sandbox.ts), this converts to `AbortSignal.timeout(timeout * 1000)` and merges with any provided `AbortSignal` using `AbortSignal.any`. If the timeout fires before completion, the `abortErrorFor` utility generates a structured `FlueError` that propagates to the caller.

### What happens when an LLM context window overflows?

When the model returns an `isContextOverflow` error, Flue's compaction module in [`packages/sdk/src/compaction.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/compaction.ts) automatically summarizes the oldest messages to free up context space. It then retries the operation using `syncHarnessMessagesSince(beforeRetry, 'retry')`, which marks the new assistant turn with `source: 'retry'` as defined in [`types.ts`](https://github.com/withastro/flue/blob/main/types.ts). This process repeats until the model stops generating or the operation succeeds.

### How are errors structured in Flue's HTTP responses?

All errors inherit from the `FlueError` class in [`packages/sdk/src/errors.ts`](https://github.com/withastro/flue/blob/main/packages/sdk/src/errors.ts). HTTP-specific subclasses like `FlueHttpError` include a `statusCode` field. When serialized, errors include `type`, `message`, `details`, and optionally `dev` (when `FLUE_MODE=local`). The [`session.ts`](https://github.com/withastro/flue/blob/main/session.ts) layer normalizes sandbox and execution errors into this format before the HTTP layer sends the JSON response.

### Can I provide my own AbortSignal alongside a timeout?

Yes. The [`sandbox.ts`](https://github.com/withastro/flue/blob/main/sandbox.ts) implementation merges the user-provided `signal` with the internally generated timeout signal using `AbortSignal.any([opts.signal, timeoutSignal])`. This allows your code to cancel operations programmatically while still respecting the automatic timeout, with either trigger resulting in a properly typed `FlueError`.