fix(approval): honor interrupt in blocking gateway approval wait (#8697)

A dangerous-command gateway approval blocks the agent's execution thread
inside _await_gateway_decision() on threading.Event.wait() until the user
responds or the 5-minute approval timeout fires. The poll loop never checked
is_interrupted(), so /stop (which flags the agent's execution thread via
AIAgent.interrupt()) was silently ignored — the session stayed wedged until
timeout, even though /stop reported the session unlocked.

Check is_interrupted() at the top of the poll loop. The wait runs on the
agent's execution thread, the exact thread interrupt() flags, so the check
sees the signal and resolves the pending approval as deny — the agent loop
receives a normal denial and unwinds cleanly. Covers /stop, /new, and the
gateway inactivity-timeout interrupt through the single shared wait loop used
by both the terminal and execute_code guards.
This commit is contained in:
panghuer023 2026-06-21 12:44:04 -07:00 committed by Teknium
parent 824c9d3812
commit a9c8025984

View file

@ -20,6 +20,7 @@ import unicodedata
from typing import Optional
from hermes_cli.config import cfg_get
from tools.interrupt import is_interrupted
from utils import env_var_enabled, is_truthy_value
logger = logging.getLogger(__name__)
@ -1343,6 +1344,23 @@ def _await_gateway_decision(session_key: str, notify_cb, approval_data: dict,
_activity_state = {"last_touch": _now, "start": _now}
resolved = False
while True:
# Respect interrupt signals (e.g. /stop, /new, or an inactivity
# timeout from the gateway) so a pending approval doesn't keep the
# session wedged on threading.Event.wait() until the 5-minute approval
# timeout. The wait runs on the agent's execution thread, which is the
# exact thread AIAgent.interrupt() flags — so is_interrupted() here
# sees the signal. Resolve as "deny" so the agent loop receives a
# normal denial and unwinds cleanly (#8697).
if is_interrupted():
logger.info(
"Approval wait interrupted by user signal — "
"returning deny for session %s",
session_key,
)
entry.result = "deny"
entry.event.set()
resolved = True
break
_remaining = _deadline - time.monotonic()
if _remaining <= 0:
break