Bug Description
When running an agent with require_approval configured and multiple tool calls executing concurrently, a RuntimeError: dictionary changed size during iteration occurs in RunContextWrapper._approvals.
Root Cause
In src/agents/run_context.py, _fork_with_tool_input and _fork_without_tool_input share the _approvals dict by reference:
def _fork_with_tool_input(self, tool_input: Any) -> RunContextWrapper[TContext]:
fork = RunContextWrapper(context=self.context)
fork._approvals = self._approvals # shared reference
...
When parallel tool calls run concurrently in an async event loop:
- One coroutine iterates over
_approvals (e.g., in _get_approval_status_for_key)
- Another coroutine calls
_get_or_create_approval_entry, which inserts a new key into the same dict
This causes the RuntimeError because the dict size changes during iteration.
Reproduction
- Create an agent with MCP server that has
require_approval set as a dict mapping tool names to "always"
- Send a prompt that triggers multiple tool calls in the same turn
- Use
state.approve(item, always_approve=True) to approve tools
- The error occurs intermittently when the SDK internally checks approval status for one tool while creating an approval entry for another
Environment
openai-agents==0.14.6
- Python 3.11
- Using
Runner.run_streamed() with MCP tools
Suggested Fix
Either:
- Use
dict.copy() when iterating in _get_approval_status_for_key and related methods
- Or use an
asyncio.Lock to protect concurrent access to _approvals
Option 1 (minimal change):
def _get_or_create_approval_entry(self, tool_name: str) -> _ApprovalRecord:
approval_entry = self._approvals.get(tool_name)
if approval_entry is None:
approval_entry = _ApprovalRecord()
self._approvals[tool_name] = approval_entry
return approval_entry
The get + conditional set pattern is safe, but the issue is when other methods iterate over self._approvals.items() or similar while this method adds a new key. Wrapping iteration sites with list(self._approvals.items()) would prevent the RuntimeError.
Bug Description
When running an agent with
require_approvalconfigured and multiple tool calls executing concurrently, aRuntimeError: dictionary changed size during iterationoccurs inRunContextWrapper._approvals.Root Cause
In
src/agents/run_context.py,_fork_with_tool_inputand_fork_without_tool_inputshare the_approvalsdict by reference:When parallel tool calls run concurrently in an async event loop:
_approvals(e.g., in_get_approval_status_for_key)_get_or_create_approval_entry, which inserts a new key into the same dictThis causes the RuntimeError because the dict size changes during iteration.
Reproduction
require_approvalset as a dict mapping tool names to"always"state.approve(item, always_approve=True)to approve toolsEnvironment
openai-agents==0.14.6Runner.run_streamed()with MCP toolsSuggested Fix
Either:
dict.copy()when iterating in_get_approval_status_for_keyand related methodsasyncio.Lockto protect concurrent access to_approvalsOption 1 (minimal change):
The
get+ conditionalsetpattern is safe, but the issue is when other methods iterate overself._approvals.items()or similar while this method adds a new key. Wrapping iteration sites withlist(self._approvals.items())would prevent the RuntimeError.