Error Handling

Handle provider, tool, approval, validation, and runtime failures differently.

Error Handling

Shiro surfaces failures by where they happen. Your application should branch on that, not on a single string message.

Categories#

CategoryMeaningTypical response
Provider errorAdapter / model call failedRetry or degrade
Tool errorexecute threw or timed outFix args / service; retry if safe
Approval rejectionReviewer denied the toolTell the user; do not force
Validation errorOutput schema .parse failedRepair or ask for clarification
Configuration errorEngine / agent misconfiguredFix startup; do not retry blindly
Runtime errorRunner cannot continue safelyFail the run; keep the trace

Core exports typed errors such as ProviderError, ToolExecutionError, ValidationError, ApprovalRejectedError, ConfigurationError, and ShiroError.

Example#

import { ApprovalRejectedError, ShiroError } from "@shiro-sdk/core";

try {
  return await engine.execute(agent, input, { sessionId });
} catch (error) {
  if (error instanceof ApprovalRejectedError) {
    return { ok: false, reason: "rejected_by_reviewer" };
  }
  if (error instanceof ShiroError) {
    // Include error.run-related context / your trace runId in logs
    throw error;
  }
  throw error;
}

Always preserve enough identity (runId, session id) to find the trace later.

Why categories matter#

Retries are not universally safe. A provider timeout mid-refund is not permission to call issueRefund again without an idempotency key. See Reliability.

Best practices#

  • Inspect the trace before retrying
  • Return stable error codes to clients
  • Treat approval rejection as product behavior
  • Normalize vendor errors inside providers

Common mistakes#

  • catch (e) { res.status(500) } for every failure mode
  • Retrying writes automatically
  • Losing traces when a run fails
  • Parsing error messages instead of using error types