The Four Error Categories
Category 1: Transient Errors
Temporary failures that resolve without fixing the request.
TRANSIENT_ERROR_CODES = {
"TIMEOUT",
"SERVICE_UNAVAILABLE",
"RATE_LIMIT_EXCEEDED",
"NETWORK_ERROR",
"TEMPORARY_FAILURE"
}
# These errors: safe to retry with exponential backoff
Examples:
- HTTP 503 Service Unavailable
- Network timeout
- Rate limit exceeded (wait, then retry)
- Database connection pool exhausted
Correct response: retry with exponential backoff
async def retry_transient(tool_call, max_retries=3):
for attempt in range(max_retries):
try:
return await execute_tool(tool_call)
except ToolError as e:
if e.category != "transient":
raise # Don't retry non-transient errors
if attempt == max_retries - 1:
raise # Last attempt, give up
await asyncio.sleep(2 ** attempt) # 1s, 2s, 4s
Category 2: Validation Errors
The request is malformed — wrong format, missing required fields, invalid values.
VALIDATION_ERROR_CODES = {
"INVALID_FORMAT",
"MISSING_REQUIRED_FIELD",
"INVALID_VALUE",
"SCHEMA_VIOLATION",
"TYPE_MISMATCH"
}
# These errors: NEVER retry with same input
# Must fix the input first
Examples:
- customer_id in wrong format (expected “C-123456”, got “123456”)
- Missing required field (amount not provided for refund)
- Invalid enum value (status=“UNKNOWN” when only “active”/“inactive” valid)
Correct response: fix the input, inform Claude of the validation error
# Return validation error to Claude so it can fix its input
return {
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps({
"error": "validation_error",
"message": "customer_id format is invalid",
"expected_format": "C-XXXXXX (e.g., C-123456)",
"received": "123456",
"isRetryable": False
}),
"is_error": True
}
Category 3: Business Rule Errors
Valid input, valid authorization, but the operation violates a business constraint.
BUSINESS_ERROR_CODES = {
"REFUND_LIMIT_EXCEEDED",
"ACCOUNT_SUSPENDED",
"POLICY_VIOLATION",
"DUPLICATE_REQUEST",
"INSUFFICIENT_FUNDS"
}
# These errors: NOT retryable, require different action
Examples:
- Refund amount exceeds $500 automated limit
- Customer account is suspended
- Duplicate refund request already processed
- Subscription cannot be cancelled during billing period
Correct response: escalate or take alternative action
return {
"error": "business_rule_violation",
"code": "REFUND_LIMIT_EXCEEDED",
"message": "Refund amount $750 exceeds the $500 automated processing limit",
"limit": 500,
"requested": 750,
"escalation_required": True,
"alternative_action": "Escalate to human agent for manual approval",
"isRetryable": False
}
Category 4: Permission Errors
The caller lacks authorization for this operation.
PERMISSION_ERROR_CODES = {
"UNAUTHORIZED",
"FORBIDDEN",
"INSUFFICIENT_PRIVILEGES",
"TOKEN_EXPIRED",
"RESOURCE_NOT_ACCESSIBLE"
}
# These errors: NOT retryable (unless token expired — then refresh and retry once)
Examples:
- Agent doesn’t have access to billing operations
- Customer data in a restricted region
- Expired API token
Correct response: fix authorization, not retry with same credentials
Structured Error Responses
Return errors in a consistent structure so both Claude and your code can handle them appropriately:
def format_tool_error(
category: str, # "transient" | "validation" | "business" | "permission"
code: str, # Specific error code
message: str, # Human-readable message
is_retryable: bool,
details: dict = None # Category-specific additional context
) -> dict:
return {
"error": True,
"category": category,
"code": code,
"message": message,
"isRetryable": is_retryable,
"details": details or {},
"timestamp": datetime.utcnow().isoformat()
}
# Usage
return format_tool_error(
category="validation",
code="INVALID_CUSTOMER_ID",
message=f"Customer ID '{customer_id}' does not match required format C-XXXXXX",
is_retryable=False,
details={"received": customer_id, "expected_pattern": "^C-[0-9]{6}$"}
)
Key Takeaways
- Transient → retry with backoff (network issues, temporary unavailability)
- Validation → fix input, never retry with same data
- Business rule → different action or escalation, not retry
- Permission → fix authorization, not retry with same credentials
- isRetryable flag in error response guides agent decision-making
- Consistent error structure enables both Claude and code to handle correctly