fix(interrupt): run user-approved commands from a clean interrupt slate

A user-approved terminal/execute_code command could be SIGINT-killed
(exit 130 + "[Command interrupted]") by a stale interrupt bit that landed
on the execution thread during the blocking approval-wait, while the
result still carried the "...approved by the user." note. The terminal
tool runs sequentially inline on the execution thread, and nothing
cleared or re-checked the bit between approval-grant and env.execute.

Clear the current thread's interrupt bit once before an approved command
spawns its child (terminal foreground; execute_code local + remote), and
enrich the note to "...approved by the user, then interrupted." on a
genuine post-start interrupt instead of implying success. A genuine
interrupt arriving after execution starts (or during a retry backoff)
still SIGINTs the command; non-approved commands keep current behavior.

Adds regression tests covering stale-bit-clears, genuine-interrupt-still-
kills, the retry-backoff window, natural-exit-130 (not mislabeled), and
execute_code local + remote.
This commit is contained in:
hellno 2026-06-19 14:41:58 +02:00 committed by Teknium
parent 8235f484c9
commit d7348bf24b
5 changed files with 346 additions and 2 deletions

View file

@ -1166,6 +1166,16 @@ def execute_code(
"duration_seconds": 0,
}, ensure_ascii=False)
# Clean interrupt slate for a user-approved script before EITHER dispatch
# path spawns it: drop a stale bit that landed on this thread during the
# blocking approval-wait so it can't kill the just-approved run on the first
# poll (local _wait_for_process loop, or remote/ssh env.execute which routes
# through the same poll loop). A genuine post-clear interrupt re-sets the
# bit and is still caught downstream.
if _guard.get("user_approved"):
from tools.interrupt import clear_current_thread_interrupt
clear_current_thread_interrupt()
if env_type != "local":
return _execute_remote(code, task_id, enabled_tools)