-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
The in_approval flag is a boolean that gates whether unleashed will attempt to auto-approve a detected permission prompt. It's set to True at the start of do_approval() and cleared to False in _sentinel_check()'s finally block (or immediately for non-sentinel approvals).
The problem: there is no watchdog, no timeout, and no recovery mechanism if the flag gets stuck at True.
# In _sentinel_check():
def _sentinel_check(self, pty, tool_type, tool_args):
try:
# ... API call, verdict handling ...
except Exception as e:
# ... fail-open ...
finally:
self.in_approval = False # <-- only reset point for sentinel pathIf anything prevents finally from executing — thread killed externally, Python interpreter issue, pty.write() hanging in the try block — in_approval stays True and every subsequent permission prompt is ignored. The user sees Claude Code asking for permission, but unleashed never sends the CR keystroke. The session appears frozen.
Reproduction Scenario
- Enable sentinel (
--sentinelor--sentinel-scope) - Trigger a permission prompt for a command that routes through sentinel
- Simulate
_sentinel_checkhanging: e.g., Haiku API returns butpty.write('\r')blocks because the PTY buffer is full finallynever executes- Next permission prompt:
in_approvalis stillTrue, so_reader_pty()skips thedo_approval()call - Session is stuck — user must Ctrl+C and restart
Why This Is Priority High
Even without sentinel, the non-sentinel do_approval() path sets in_approval = True before the time.sleep(0.1) + pty.write('\r') sequence. If pty.write raises (PTY died), in_approval stays True. The flag is never checked against time — there's no "if it's been True for >10 seconds, something is wrong."
This is a reliability issue first and a sentinel issue second. The flag predates sentinel integration.
Proposed Fix
Add a watchdog in _reader_pty():
# In the permission detection block:
if self.in_approval:
# Watchdog: if in_approval has been True for >15s, force-reset
if hasattr(self, '_approval_started') and time.time() - self._approval_started > 15:
log("WATCHDOG: in_approval stuck for >15s, force-resetting")
sys.stderr.write("\n\033[93m[UNLEASHED] Approval timeout — resetting\033[0m\n")
self.in_approval = False
continueAnd timestamp when approval starts:
def do_approval(self, pty, tool_type="unknown", tool_args=""):
self.in_approval = True
self._approval_started = time.time()
# ...15 seconds is generous — even a slow Haiku call with retry would complete in <10s. If we're still in approval after 15s, something is broken.
Affected Files
src/unleashed-c-21.py—do_approval(),_sentinel_check(),_reader_pty()approval detection block
Context
The in_approval flag is the primary serialization mechanism for unleashed's auto-approval system. It prevents double-approval (sending multiple CRs for one prompt) and prevents the PTY reader from processing output that arrives between the permission detection and the CR send. But a flag without a timeout is a latch without a spring — if it gets stuck, the whole mechanism stops. Every long-running unleashed session is one edge case away from this.