Study 59 files · 8 subfolders
Copy to Workspace 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
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);
}
}
throw new ClientError (400 , ErrorType .BAD_REQUEST );
throw new ClientError (401 , ErrorType .UNAUTHORIZED );
throw new ClientError (429 , ErrorType .RATE_LIMIT_EXCEEDED );
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);
}
}
throw new ServerError (500 , ErrorType .LLM_PROVIDER_ERROR );
throw new ServerError (502 , ErrorType .SCRAPING_FAILED );
throw new ServerError (503 , ErrorType .DATABASE_ERROR );
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;
}
}
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 ;
}
}
throw new TaskError (ErrorType .TASK_EXECUTION_FAILED , Severity .ERROR , {
taskId,
});
Error Types & Severity File: src/server/constantsSchemasAndTypes/global/globalConstants.ts
TypeScript
export enum ErrorType {
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" ,
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" ,
MISSING_BEARER_TOKEN = "MISSING_BEARER_TOKEN" ,
INVALID_TOKEN = "INVALID_TOKEN" ,
WEB_SEARCH_FAILED = "WEB_SEARCH_FAILED" ,
}
export enum Severity {
INFO = "INFO" ,
WARNING = "WARNING" ,
ERROR = "ERROR" ,
CRITICAL = "CRITICAL" ,
}
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" ,
};
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 ,
errorHandler ?: (error : any ) => void ,
): Promise <Awaited <ReturnType <T>>> {
const totalRetryCount = currentRetryCount;
let result;
while (currentRetryCount > 0 ) {
try {
result = await asyncFunc ({
retryCount : totalRetryCount - currentRetryCount,
});
break ;
} catch (error) {
if (errorHandler) {
errorHandler (error);
}
currentRetryCount--;
if (currentRetryCount === 0 ) {
throw error;
}
}
}
return result;
}TypeScript
const result = await retryAsyncFunction (
async (context) => {
return await scrapeUrl (url, {
usePremiumProxy : context.retryCount > 1 ,
renderJs : context.retryCount > 0 ,
});
},
3 ,
(error ) => {
if (error.response ?.status >= 400 && error.response ?.status < 500 ) {
throw error;
}
},
);
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);
};
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 };
}
const { fulfilled, rejected } = await settleAll ([
handleWebSearchStep (step1, ...),
handleWebSearchStep (step2, ...),
handleWebSearchStep (step3, ...),
]);
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]> };
}
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
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;
}
} catch {
logger.error (`ERROR/WEB_SEARCH/${step.searchEngine} _SEARCH` );
}
}
logger.error (`ERROR/WEB_SEARCH_FAILED` );
return [];
Scraping Tier Fallback TypeScript
try {
return await scrap (url);
} catch (error) {
try {
return await firecrawl.scrapeUrl (url);
} catch {
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)) {
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) {
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 ) {
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 ;
}
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 {
const searchResults = await generateSerpQueries (step, ...);
} catch (error) {
logger.error (error, "ERROR/DEEP_RESEARCH/SERP_QUERIES_GENERATION" );
searchResults = { serpResults : { research_pairs : [] }, webResults : [] };
}
try {
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 {
if (stepConfidence > 0.6 ) {
const { insights, gaps } = await ResearchMemoryManager .extractInsightsAndGaps (step, newResults);
}
} catch (error) {
logger.error (error, "ERROR/DEEP_RESEARCH/EXTRACT_INSIGHTS_AND_GAPS" );
}
}
}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 ) => {
delete error.config ?.httpAgent ;
delete error.config ?.httpsAgent ;
return Promise .reject (error);
},
);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:
Typed Errors : ClientError, ServerError, PromptError, TaskError
Severity Levels : INFO, WARNING, ERROR, CRITICAL
Automatic Retry : retryAsyncFunction with context
Progressive Enhancement : Retry count drives behavior changes
Settle Patterns : settleAll (continue), settleAllOrThrow (fail)
Circuit Breakers : Provider cascades, tier fallbacks
Tool Isolation : One tool error doesn't crash orchestrator
Deep Research Resilience : Every step wrapped in try-catch
Graceful Degradation : Empty results instead of crashes
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.