fix(agent): propagate approval callbacks to concurrent tool worker threads
When tools execute concurrently via ThreadPoolExecutor, worker threads could not see the thread-local approval/sudo callbacks registered by the CLI. This caused dangerous-command prompts to fall back to plain input(), which deadlocks against prompt_toolkit's raw terminal mode. Capture parent-thread callbacks before launching workers, register them locally in each _run_tool thread, and clear them on exit. Mirrors the existing fix pattern from cli.py run_agent() for the main agent worker thread (GHSA-qg5c-hvr5-hjgr / #13617).
This commit is contained in:
parent
8ad29a938a
commit
0046d170dc
33
run_agent.py
33
run_agent.py
@ -74,6 +74,12 @@ from model_tools import (
|
||||
check_toolset_requirements,
|
||||
)
|
||||
from tools.terminal_tool import cleanup_vm, get_active_env, is_persistent_env
|
||||
from tools.terminal_tool import (
|
||||
set_approval_callback as _set_approval_callback,
|
||||
set_sudo_password_callback as _set_sudo_password_callback,
|
||||
_get_approval_callback,
|
||||
_get_sudo_password_callback,
|
||||
)
|
||||
from tools.tool_result_storage import maybe_persist_tool_result, enforce_turn_budget
|
||||
from tools.interrupt import set_interrupt as _set_interrupt
|
||||
from tools.browser_tool import cleanup_browser
|
||||
@ -8649,6 +8655,14 @@ class AIAgent:
|
||||
self._current_tool = tool_names_str
|
||||
self._touch_activity(f"executing {num_tools} tools concurrently: {tool_names_str}")
|
||||
|
||||
# Capture CLI callbacks from the agent thread so worker threads can
|
||||
# register them locally. Without this, _get_approval_callback() in
|
||||
# terminal_tool returns None in ThreadPoolExecutor workers, causing
|
||||
# the dangerous-command prompt to fall back to input() — which
|
||||
# deadlocks against prompt_toolkit's raw terminal mode (#13617).
|
||||
_parent_approval_cb = _get_approval_callback()
|
||||
_parent_sudo_cb = _get_sudo_password_callback()
|
||||
|
||||
def _run_tool(index, tool_call, function_name, function_args):
|
||||
"""Worker function executed in a thread."""
|
||||
# Register this worker tid so the agent can fan out an interrupt
|
||||
@ -8675,6 +8689,18 @@ class AIAgent:
|
||||
set_activity_callback(self._touch_activity)
|
||||
except Exception:
|
||||
pass
|
||||
# Propagate approval/sudo callbacks to this worker thread.
|
||||
# Mirrors cli.py run_agent() pattern (GHSA-qg5c-hvr5-hjgr).
|
||||
if _parent_approval_cb is not None:
|
||||
try:
|
||||
_set_approval_callback(_parent_approval_cb)
|
||||
except Exception:
|
||||
pass
|
||||
if _parent_sudo_cb is not None:
|
||||
try:
|
||||
_set_sudo_password_callback(_parent_sudo_cb)
|
||||
except Exception:
|
||||
pass
|
||||
start = time.time()
|
||||
try:
|
||||
result = self._invoke_tool(function_name, function_args, effective_task_id, tool_call.id, messages=messages)
|
||||
@ -8697,6 +8723,13 @@ class AIAgent:
|
||||
_set_interrupt(False, _worker_tid)
|
||||
except Exception:
|
||||
pass
|
||||
# Clear thread-local callbacks so a recycled worker thread
|
||||
# doesn't hold stale references to a disposed CLI instance.
|
||||
try:
|
||||
_set_approval_callback(None)
|
||||
_set_sudo_password_callback(None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Start spinner for CLI mode (skip when TUI handles tool progress)
|
||||
spinner = None
|
||||
|
||||
Loading…
Reference in New Issue
Block a user