InkdownInkdown
Start writing

Study

59 files·8 subfolders

Shared Workspace

Study
core

08-ErrorHandling

Shared from "Study" on Inkdown

Error Handling Architecture

Overview

The error handling system provides robust resilience through typed errors, automatic retry logic, circuit breakers, and graceful degradation. It ensures system stability even when external services fail.


Error Hierarchy

Plain text
Error (Base)
    └── TypedError (Abstract)
            ├── ClientError (4xx)
            │       ├── 400: Bad Request
            │       ├── 401: Unauthorized
            │       ├── 403: Forbidden
            │       ├── 404: Not Found
            │       └── 429: Rate Limited
            │
            ├── ServerError (5xx)
            │       ├── 500: Internal Server Error
            │       ├── 502: Bad Gateway
            │       ├── 503: Service Unavailable
            │       └── 504: Gateway Timeout
            │
            └── Specialized Errors
                    ├── PromptError (LLM refusal/incomplete)
                    └── TaskError (Task execution failure)
programming-language-concepts.md
zero-language-explanation.md
DB
01-introduction.md
02-relational-databases.md
03-database-design.md
04-indexing.md
05-transactions-acid.md
06-nosql-databases.md
07-query-optimization.md
08-replication-ha.md
09-sharding-partitioning.md
10-caching-strategies.md
11-cap-theorem.md
12-connection-pooling.md
13-backup-recovery.md
14-monitoring.md
15-database-selection.md
README.md
JS
Event loop
Merlin Backend
01-Orchestration.md
02-DeepResearch.md
03-Search.md
04-Scraping.md
05-Streaming.md
06-MultiProviderLLM.md
07-MemoryAndContext.md
08-ErrorHandling.md
09-RateLimiting.md
10-TaskQueue.md
11-SecurityAndAuth.md
Orchestration-2nd-draft
OpenAI Agents Python
00_OVERVIEW.md
01_AGENT_SYSTEM.md
02_RUNNER_SYSTEM.md
03_TOOL_SYSTEM.md
04_ITEMS_SYSTEM.md
05_GUARDRAILS.md
06_HANDOFFS.md
07_MEMORY_SESSIONS.md
08_MODEL_PROVIDERS.md
09_SANDBOX_SYSTEM.md
10_TRACING.md
11_RUN_STATE.md
12_CONTEXT.md
13_LIFECYCLE_HOOKS.md
14_CONFIGURATION.md
15_ERROR_HANDLING.md
16_STREAMING.md
17_EXTENSIONS.md
18_MCP_INTEGRATION.md
19_BEST_PRACTICES.md
20_ARCHITECTURE_PATTERNS.md
opencode-study
context-handling
core
Python
Alembic
Basics
sqlalchemy - fastapi
SQLAlchemy overview
tweets
system_design_for_agentic_apps.md

Typed Error Base Classes

File: src/server/models/error/error.ts

TypeScript
abstract class TypedError extends Error {
	severity: Severity;
	statusCode: IntRange<400, 600>;
	errorType: ErrorType;

	constructor(
		statusCode: IntRange<400, 600>,
		errorType: ErrorType,
		severity: Severity,
		cause?: ErrorOptions["cause"],
	) {
		super(ErrorMessage[errorType], { cause: cause });
		this.statusCode = statusCode;
		this.errorType = errorType;
		this.severity = severity;
	}
}
Client Error (User-Facing)
TypeScript
export class ClientError extends TypedError {
	constructor(
		statusCode: IntRange<400, 500>,
		type: ErrorType,
		severity: Severity = Severity.WARNING,
		cause?: ErrorOptions["cause"],
	) {
		super(statusCode, type, severity, cause);
	}
}

// Usage examples:
throw new ClientError(400, ErrorType.BAD_REQUEST); // Invalid input
throw new ClientError(401, ErrorType.UNAUTHORIZED); // Auth failure
throw new ClientError(429, ErrorType.RATE_LIMIT_EXCEEDED); // Too many requests
Server Error (Internal)
TypeScript
export class ServerError extends TypedError {
	constructor(
		statusCode: IntRange<500, 600>,
		type: ErrorType,
		severity: Severity = Severity.ERROR,
		cause?: ErrorOptions["cause"],
	) {
		super(statusCode, type, severity, cause);
	}
}

// Usage examples:
throw new ServerError(500, ErrorType.LLM_PROVIDER_ERROR); // AI service down
throw new ServerError(502, ErrorType.SCRAPING_FAILED); // Scraper unavailable
throw new ServerError(503, ErrorType.DATABASE_ERROR); // DB connection lost
Prompt Error (LLM Interaction)
TypeScript
export class PromptError extends Error {
	reason: string;
	errorType: ErrorType;

	constructor(type: ErrorType, refusal: { fields: string[]; reason: string }) {
		const reason = prompts.withRuleTags(`
            ${
							refusal.fields?.length > 0
								? `Inform user of this error: Merlin Unable to infer fields: ${refusal.fields.join(", ")}
                   Reasoning: ${refusal.reason}`
								: ""
						}
        `);

		super(ErrorMessage[type], { cause: reason });
		this.errorType = type;
		this.reason = reason;
	}
}

// Usage: When LLM can't understand user intent
throw new PromptError(ErrorType.INCOMPLETE_TASK, {
	fields: ["schedule", "priority"],
	reason: "User query is ambiguous about when and how important",
});
Task Error (Background Jobs)
TypeScript
export class TaskError extends Error {
	severity: Severity;
	errorType: ErrorType;

	constructor(
		errorType: ErrorType,
		severity?: Severity,
		cause?: ErrorOptions["cause"],
	) {
		super(ErrorMessage[errorType], { cause: cause });
		this.errorType = errorType;
		this.severity = severity ?? Severity.ERROR;
	}
}

// Usage: Task execution failure
throw new TaskError(ErrorType.TASK_EXECUTION_FAILED, Severity.ERROR, {
	taskId,
});

Error Types & Severity

File: src/server/constantsSchemasAndTypes/global/globalConstants.ts

TypeScript
export enum ErrorType {
	// 4xx Client Errors
	BAD_REQUEST = "BAD_REQUEST",
	UNAUTHORIZED = "UNAUTHORIZED",
	FORBIDDEN = "FORBIDDEN",
	NOT_FOUND = "NOT_FOUND",
	RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED",
	INPUT_NOT_MODERATED = "INPUT_NOT_MODERATED",
	CONTENT_FILTERED = "CONTENT_FILTERED",
	INCOMPLETE_TASK = "INCOMPLETE_TASK",

	// 5xx Server Errors
	INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR",
	LLM_PROVIDER_ERROR = "LLM_PROVIDER_ERROR",
	SCRAPING_FAILED = "SCRAPING_FAILED",
	DATABASE_ERROR = "DATABASE_ERROR",
	CACHE_ERROR = "CACHE_ERROR",
	TASK_EXECUTION_FAILED = "TASK_EXECUTION_FAILED",

	// Special
	MISSING_BEARER_TOKEN = "MISSING_BEARER_TOKEN",
	INVALID_TOKEN = "INVALID_TOKEN",
	WEB_SEARCH_FAILED = "WEB_SEARCH_FAILED",
}

export enum Severity {
	INFO = "INFO", // Log only
	WARNING = "WARNING", // Log + alert if repeated
	ERROR = "ERROR", // Log + alert + investigate
	CRITICAL = "CRITICAL", // Immediate escalation
}

export const ErrorMessage: Record<ErrorType, string> = {
	[ErrorType.BAD_REQUEST]: "The request could not be understood",
	[ErrorType.UNAUTHORIZED]: "Authentication required",
	[ErrorType.RATE_LIMIT_EXCEEDED]: "Too many requests. Please slow down",
	[ErrorType.LLM_PROVIDER_ERROR]: "AI service temporarily unavailable",
	[ErrorType.SCRAPING_FAILED]: "Unable to fetch content from website",
	// ... etc
};

Retry Architecture

File: src/server/utilities/retry.ts

Core Retry Function
TypeScript
export async function retryAsyncFunction<
	T extends (context: TRetryContext) => any,
>(
	asyncFunc: T,
	currentRetryCount = DEFAULT_RETRIES, // Default: 3
	errorHandler?: (error: any) => void, // Custom error handling
): Promise<Awaited<ReturnType<T>>> {
	const totalRetryCount = currentRetryCount;
	let result;

	while (currentRetryCount > 0) {
		try {
			// Execute with retry context
			result = await asyncFunc({
				retryCount: totalRetryCount - currentRetryCount, // 0, 1, 2...
			});
			break; // Success - exit loop
		} catch (error) {
			if (errorHandler) {
				errorHandler(error); // Custom handling
			}

			currentRetryCount--;

			if (currentRetryCount === 0) {
				throw error; // Exhausted - throw original
			}
		}
	}

	return result;
}

Usage Example:

TypeScript
const result = await retryAsyncFunction(
	async (context) => {
		// Progressive enhancement
		return await scrapeUrl(url, {
			usePremiumProxy: context.retryCount > 1,
			renderJs: context.retryCount > 0,
		});
	},
	3, // 3 retries
	(error) => {
		// Don't retry 4xx errors
		if (error.response?.status >= 400 && error.response?.status < 500) {
			throw error; // Fail fast
		}
	},
);
Retry with Higher-Order Function
TypeScript
export const withRetry = <T, Args extends any[]>(
	fn: TAsyncFunction<T, Args>,
	retries?: number,
): TAsyncFunction<T, Args> => {
	retries = retries ?? DEFAULT_RETRIES;
	return (...args: Args) => retryAsyncFunction(() => fn(...args), retries);
};

// Usage
const fetchWithRetry = withRetry(fetchData, 3);
const result = await fetchWithRetry(url);
Parallel Execution with Settling

All Settled (Don't Throw):

TypeScript
export type TSettledResults<T extends readonly unknown[]> = {
    fulfilled: { [K in keyof T]: Awaited<T[K]> };
    rejected: unknown[];
};

export async function settleAll<T extends readonly unknown[]>(
    promises: readonly [...{ [K in keyof T]: Promise<T[K]> }],
): Promise<TSettledResults<T>> {
    if (!Array.isArray(promises) || promises.length === 0) {
        return { fulfilled: [] as any, rejected: [] };
    }

    const results = await Promise.allSettled(promises);

    const fulfilled = results
        .map(result => result.status === "fulfilled" ? result.value : undefined)
        .filter((result): result is NonNullable<typeof result> => result !== undefined) as {
            [K in keyof T]: Awaited<T[K]>;
        };

    const rejected = results
        .filter(result => result.status === "rejected")
        .map(result => (result as PromiseRejectedResult).reason);

    return { fulfilled, rejected };
}

// Usage: Deep research parallel steps
const { fulfilled, rejected } = await settleAll([
    handleWebSearchStep(step1, ...),
    handleWebSearchStep(step2, ...),
    handleWebSearchStep(step3, ...),
]);
// fulfilled: successful results
// rejected: errors (logged but don't crash)

All Settled Throw on Error:

TypeScript
export async function settleAllOrThrow<T extends readonly unknown[]>(
	promises: readonly [...{ [K in keyof T]: Promise<T[K]> }],
): Promise<{ [K in keyof T]: Awaited<T[K]> }> {
	const { fulfilled, rejected } = await settleAll(promises);

	if (rejected.length > 0) {
		throw new AggregateError(rejected, "PROMISES_REJECTED");
	}

	return fulfilled as { [K in keyof T]: Awaited<T[K]> };
}

// Usage: When all must succeed
const [result1, result2] = await settleAllOrThrow([
	criticalOperation1(),
	criticalOperation2(),
]);

Circuit Breaker Pattern

While not explicit circuit breakers, the system uses fail-fast patterns:

Search Provider Cascade
TypeScript
// Try each provider, return first success
for (const step of searchEngineFlow) {
	try {
		const handler = searchEngineHandlers[step.searchEngine];
		const results = await handler(query, step.timeout, step.retries, metadata);

		if (results.length > 0) {
			return results; // Success - stop trying
		}
	} catch {
		logger.error(`ERROR/WEB_SEARCH/${step.searchEngine}_SEARCH`);
		// Continue to next provider (failover)
	}
}

// All providers failed
logger.error(`ERROR/WEB_SEARCH_FAILED`);
return []; // Graceful degradation
Scraping Tier Fallback
TypeScript
// Tier 1: ScrapingBee
try {
	return await scrap(url);
} catch (error) {
	// Tier 2: Firecrawl
	try {
		return await firecrawl.scrapeUrl(url);
	} catch {
		// Tier 3: Direct fetch
		return await basicFetch(url);
	}
}

Orchestrator Error Handling

File: src/server/endpoints/unified/orchestrator/toolOrchestrator.ts

Tool-Level Error Isolation
TypeScript
private runSingleTool(name, id, args, progress, agentContext, index) {
    return async function* () {
        yield { type: "tool:start", id, function: { name }, progress };

        try {
            const parsedArgs = JSON.parse(args);
            const resultOrStream = await tool.execute(parsedArgs, ctx, progress, toolContext);

            let finalResult;
            if (isAsyncIterable(resultOrStream)) {
                // Handle streaming
                const iterator = resultOrStream[Symbol.asyncIterator]();
                while (true) {
                    const { value, done } = await iterator.next();
                    if (done) { finalResult = value; break; }
                    if ("stream" in value) {
                        yield { type: "tool:stream", id, function: { name }, content: value };
                    }
                }
            } else {
                finalResult = resultOrStream;
            }

            yield { type: "tool:done", id, function: { name }, result: finalResult };

        } catch (e) {
            // ISOLATED: Tool error doesn't crash orchestrator
            logger.warn({ error: e, args, name, id }, `WARN/TOOL/RUN-SINGLE-TOOL`);
            yield {
                type: "tool:error",
                id,
                function: { name },
                error: e instanceof Error ? e.message : String(e),
            };
        }
    };
}
Invalid Tool Name Recovery
TypeScript
const toolCallParsed = multipleToolCallSchema.safeParse(
	streamOutput?.toolCalls,
);

if (!toolCallParsed.success) {
	// Schema validation failed
	state.inLoopTrimmedMessages.push(
		SystemMessage({
			content: [
				{
					text: prompts.getInvalidToolNamePrompt(toolCallParsed.error.message),
					type: "TEXT",
					tokens: (await tokenizer.encode(toolCallParsed.error.message)).length,
				},
			],
		}),
	);

	state.hasToolCalls = true;
	continue; // Retry iteration with error message
}

Deep Research Error Resilience

File: src/server/endpoints/unified/features/deepResearch/deepResearch.ts

Step-Level Error Handling
TypeScript
async function handleWebSearchStep(step, ...): Promise<void> {
    let iterationCount = 0;
    const maxIterations = 5;

    while (!isStepComplete && iterationCount <= maxIterations) {
        iterationCount++;

        try {
            // Attempt search
            const searchResults = await generateSerpQueries(step, ...);
        } catch (error) {
            logger.error(error, "ERROR/DEEP_RESEARCH/SERP_QUERIES_GENERATION");
            // Create minimal results to continue
            searchResults = { serpResults: { research_pairs: [] }, webResults: [] };
        }

        try {
            // Process results
            const processedResults = await settleAll(
                searchResults.webResults.map(r => processSearchResult(r, step, ...))
            );
        } catch (error) {
            logger.error(error, "ERROR/DEEP_RESEARCH/PROCESS_SEARCH_RESULTS");
            processedResults = { fulfilled: [], rejected: [] };
        }

        try {
            // Extract insights
            if (stepConfidence > 0.6) {
                const { insights, gaps } = await ResearchMemoryManager.extractInsightsAndGaps(step, newResults);
            }
        } catch (error) {
            logger.error(error, "ERROR/DEEP_RESEARCH/EXTRACT_INSIGHTS_AND_GAPS");
        }

        // Continue even if some operations failed
    }
}

Pattern: Every major operation has try-catch. Log error, use fallback, continue execution.


Axios Error Handling

File: src/server/services/axios.ts

TypeScript
axiosInstance.interceptors.response.use(
	(response) => response,
	(error) => {
		// Remove agents (security)
		delete error.config?.httpAgent;
		delete error.config?.httpsAgent;
		return Promise.reject(error);
	},
);

Provider Error Mapping:

TypeScript
export const handleAxiosError = async (error: AxiosError): Promise<Error> => {
	const response = error.response;

	if (!response) {
		return new Error("NETWORK_ERROR");
	}

	const errorData = response.data as {
		error?: { message?: string; code?: string };
	};

	switch (response.status) {
		case 400:
			if (errorData.error?.code === "content_filter") {
				return new ClientError(400, ErrorType.CONTENT_FILTERED);
			}
			return new ClientError(400, ErrorType.BAD_REQUEST);

		case 401:
			return new ClientError(401, ErrorType.UNAUTHORIZED);

		case 429:
			return new ClientError(429, ErrorType.RATE_LIMITED);

		case 500:
		case 502:
		case 503:
			return new ServerError(response.status, ErrorType.LLM_PROVIDER_ERROR);

		default:
			return new Error(`UNKNOWN_ERROR: ${response.status}`);
	}
};

Summary

The error handling architecture:

  1. Typed Errors: ClientError, ServerError, PromptError, TaskError
  2. Severity Levels: INFO, WARNING, ERROR, CRITICAL
  3. Automatic Retry: retryAsyncFunction with context
  4. Progressive Enhancement: Retry count drives behavior changes
  5. Settle Patterns: settleAll (continue), settleAllOrThrow (fail)
  6. Circuit Breakers: Provider cascades, tier fallbacks
  7. Tool Isolation: One tool error doesn't crash orchestrator
  8. Deep Research Resilience: Every step wrapped in try-catch
  9. Graceful Degradation: Empty results instead of crashes
  10. Security: Remove sensitive data from error objects

Key Principle: Fail gracefully, never crash the conversation. Log everything, alert on patterns, degrade service rather than deny it.