Handling Errors, Timeouts, and Retries in Flue Agent Operations
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. 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 withFLUE_MODE=local - Structured metadata (
type, optionalmeta) enables programmatic error handling on the client side
// 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, 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.
// 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 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 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.
// 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) 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 detects this condition and automatically retries the operation after summarizing older messages.
The retry flow follows three steps:
- Detection – After each model call, Flue checks for
isContextOverflow - Compaction –
compactContext()creates a summary prompt and updates the session's message list in-place - Retry –
syncHarnessMessagesSince(beforeRetry, 'retry')re-synchronizes the harness state, marking the new turn withsource: 'retry'as defined inpackages/sdk/src/types.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.
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.
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.
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
FlueErrorinpackages/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.tsconvert numeric timeouts toAbortSignal.timeoutinstances and merge them with user signals usingAbortSignal.any. - Abort Handling: The
abortErrorForutility inabort.tstransforms aborted signals into typedFlueErrorinstances that propagate throughpackages/sdk/src/session.ts. - Auto-Retry: The compaction subsystem in
packages/sdk/src/compaction.tsdetectsisContextOverflow, summarizes older messages, and retries viasyncHarnessMessagesSince(beforeRetry, 'retry'), marking retried turns withsource: 'retry'fromtypes.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, 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 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. 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. 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →