How to Handle and Customize Error Handling with RunErrorHandlers in openai-agents-python
RunErrorHandlers in openai-agents-python provide pluggable hooks that let you intercept runtime errors like MaxTurnsExceeded and return custom final outputs, with optional control over whether the fallback appears in conversation history.
The openai-agents-python SDK includes a powerful extension point for graceful degradation when agent runs hit limits. By implementing RunErrorHandlers, you can customize how the framework responds to specific runtime errors, ensuring your agents fail gracefully with meaningful outputs rather than abrupt exceptions.
What Are RunErrorHandlers?
RunErrorHandlers are pluggable hooks defined in src/agents/run_error_handlers.py that let you decide what happens when the agent runtime raises a known error. Currently, the framework only supports handling MaxTurnsExceeded through the "max_turns" key.
The handler receives a rich snapshot of the run via RunErrorHandlerInput (which contains RunErrorData) and can return several types of values:
- RunErrorHandlerResult – Provides full control with explicit
final_outputandinclude_in_historyfields. - dict – A shortcut containing
final_outputand optionallyinclude_in_history; converted automatically toRunErrorHandlerResult. - Any other value – Treated as
final_outputwith default settings (include_in_history=True). - None – Indicates no handling; the runtime treats the error as unhandled and aborts.
The core type definitions live in src/agents/run_error_handlers.py:
RunErrorDatacaptures the snapshot of the run state [source].RunErrorHandlerInputwraps the error, context, and run data [source].RunErrorHandlerResultdefines the expected return shape [source].RunErrorHandlersis aTypedDictkeyed by error kind [source].
How Handlers Are Invoked in the Runtime
When the turn counter exceeds max_turns, the run loop in src/agents/run_internal/run_loop.py triggers the error handling pipeline.
At lines 874-904, the code builds a RunErrorData snapshot and calls resolve_run_error_handler_result:
# src/agents/run_internal/run_loop.py#L874-L904
if current_turn > max_turns:
...
run_error_data = build_run_error_data(...)
handler_result = await resolve_run_error_handler_result(
error_handlers=error_handlers,
error=max_turns_error,
context_wrapper=context_wrapper,
run_data=run_error_data,
)
The helper function resolve_run_error_handler_result in src/agents/run_internal/error_handlers.py performs three critical operations:
- Detects the
"max_turns"handler in the provided mapping. - Awaits the result if the handler is a coroutine.
- Normalizes the return value into a
RunErrorHandlerResult.
If the handler returns a result, the runtime validates the final_output against the agent’s output schema using validate_handler_final_output (lines 80-106), formats it to a string via format_final_output_text, and injects the synthesized message back into the run while respecting the include_in_history setting.
Attaching Custom Error Handlers
You pass error handlers to the error_handlers argument of any public runner entry point in src/agents/run.py:
Here is a basic synchronous handler that returns a fallback string when turns are exhausted:
from agents import Runner, Agent
from agents.run_error_handlers import RunErrorHandlers
agent = Agent(name="summarizer", output_type=str)
def max_turns_handler(data):
# Access run state via data.run_data (input, history, new items, etc.)
return "I ran out of turns, here is a brief answer."
handlers: RunErrorHandlers = {"max_turns": max_turns_handler}
result = Runner.run_sync(
starting_agent=agent,
input="Give me a step-by-step guide to bake a cake.",
max_turns=2, # Force the error
error_handlers=handlers,
)
print(result.final_output)
# → I ran out of turns, here is a brief answer.
Async Handlers and Structured Output Validation
Handlers may be regular functions or coroutines. The runtime automatically awaits async handlers, making it easy to perform I/O operations like database lookups or logging:
import asyncio
from agents import Runner, Agent
from agents.run_error_handlers import RunErrorHandlers
agent = Agent(name="structured", output_type=dict)
async def async_handler(data):
await asyncio.sleep(0.1) # Simulate I/O
return {"final_output": {"status": "timeout", "summary": "Task incomplete."}}
handlers: RunErrorHandlers = {"max_turns": async_handler}
result = await Runner.run(
starting_agent=agent,
input="Explain the theory of relativity in depth.",
max_turns=1,
error_handlers=handlers,
)
If the final_output does not match the agent’s declared output_type, validate_handler_final_output raises a UserError. This prevents corrupted structured outputs from propagating through your application.
Controlling Conversation History
The include_in_history boolean determines whether the fallback message persists in the agent's history:
True(default): The synthesized message appears as a normal assistant message. Use this when the fallback represents a logical continuation, such as a concise summary of partial work.False: The message returns to the caller but is excluded from history. Use this for generic error messages, debugging information, or when you want the output to remain opaque to downstream tools.
def silent_handler(data):
return {
"final_output": "Sorry – the operation timed out.",
"include_in_history": False, # Excludes from conversation history
}
handlers = {"max_turns": silent_handler}
Summary
- RunErrorHandlers are defined in
src/agents/run_error_handlers.pyand provide aTypedDictinterface for customizing error recovery. - The runtime invokes handlers via
resolve_run_error_handler_resultinsrc/agents/run_internal/error_handlers.pywhenmax_turnsis exceeded. - Handlers can be sync or async, and must return values compatible with the agent’s
output_typeto pass validation. - Runner.run, run_sync, and run_streamed accept the
error_handlersmapping to inject your custom logic. - Use
include_in_history=Falseto return fallback outputs without polluting the conversation context.
Frequently Asked Questions
What errors can RunErrorHandlers currently handle in openai-agents-python?
Currently, RunErrorHandlers only support the "max_turns" key, which maps to the MaxTurnsExceeded error. The framework may expand to support additional error types in future releases, but the current implementation focuses exclusively on graceful handling when agents exceed their turn limits.
Can I use async functions as RunErrorHandlers?
Yes. The runtime in src/agents/run_internal/error_handlers.py automatically detects if your handler is a coroutine and awaits it. This allows you to perform asynchronous operations like logging to external services, querying databases, or fetching context before returning the final output.
What happens if my handler returns a value that doesn't match the agent's output_type?
The runtime validates handler outputs using validate_handler_final_output in src/agents/run_internal/error_handlers.py. If the returned value doesn't conform to the agent's declared schema, the SDK raises a UserError immediately. This validation ensures structured outputs remain consistent even when error handlers intervene.
When should I set include_in_history to False?
Set include_in_history=False when your fallback message is purely informational (like "Service temporarily unavailable") or contains debugging data that downstream tools shouldn't see. Set it to True when the fallback represents a valid partial completion of the task, such as a summary of work done before hitting the turn limit, allowing the conversation to continue naturally.
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 →