Last time I wrote about the morning my Wi-Fi was dead for five hours. This post is in the same "stop babysitting an unattended machine" vein: a small daemon that presses Enter on Claude Code's Computer Use approval dialog for you. My first attempt, launched from launchd, produced nothing but nine osascript timeout lines at 22-second intervals and never sent a single keystroke. Once I moved it to run as a child of Terminal, it went from zero approvals to actually delivering \r to the dialog within seconds.
The problem: pressing "Allow for this session" by hand, every time
When you use Computer Use (screen control) in Claude Code, a confirmation dialog appears: Computer Use wants to control these apps. Selecting "Allow for this session" and pressing Enter gets you through, but it comes back every single time you cross a session boundary. I wanted to know whether it could be made permanent via settings, so I ran strings on the binary to check.
computerUseMcpState.allowedApps(セッション内メモリ)にしか積まれず初期値は空
settings.json / ~/.claude.json に事前付与キーは無い
dialog kind=computer_use_approval(requestDialog系)で PermissionRequest hookも通らない
bypassPermissionsModeAccepted: true でも出る
That's my field log from tracing the claude 2.1.266 binary with strings. The approval state lives only in session memory, in computerUseMcpState.allowedApps, and vanishes the moment you cross a process boundary. The same investigation did turn up settings that can be made permanent: bypassPermissionsModeAccepted: true in ~/.claude.json and projects[*].hasTrustDialogAccepted: true (I set 85 of them in one pass) both work, but they only suppress the "dangerous operation" confirmation, which is a different thing from the Computer Use approval dialog. In other words, there's no room to fix this on the settings side. That was the conclusion strings nailed down.
Note: If you put a
*in the middle of anallowrule string (for example grep's[^}]*regex), you get a "wildcard before the rest" warning and the rule is rejected. Putting the wildcard at the end is the safe option.
If settings won't do it, the only option left is to look at the screen and press Enter on the human's behalf. So I wrote ~/.claude/scripts/cu_dialog_autoallow.py.
Implementation: poll every Terminal tab, and don't misfire
What it does is simple: poll every tab in Apple Terminal every 2 seconds, and when the dialog string shows up, send \r. But "see it, press Enter immediately" causes accidents. The implementation leans heavily toward misfire prevention.
READ = f'''
tell application "Terminal"
set out to ""
repeat with w in windows
set k to count of tabs of w
repeat with i from 1 to k
set c to contents of tab i of w
set out to out & (tty of tab i of w) & "{SEP}" & c & "{SEP}"
end repeat
end repeat
return out
end tell
'''
The history property is heavy, around 600KB per tab, so I read only the visible screen (contents of tab i of w). There's also a trap: if you iterate with repeat with t in tabs and try to grab contents of t, you get the tab object itself back. You have to index explicitly in the form tab i of w.
The core of misfire prevention is checking which option the cursor is pointing at before firing.
def selected_is_allow(text):
# the highlighted option line starts with the pointer glyph
tail = text.split("Enter to confirm")[0]
lines = [l.strip() for l in tail.splitlines() if l.strip()]
for l in reversed(lines[-6:]):
if l.startswith("❯") or l.startswith(">"):
return "Allow for this session" in l
return False
The line starting with ❯ (or > for ASCII-only environments) is the pointer line, and the only thing checked is whether it contains "Allow for this session". If the dialog's default cursor is on the Deny side and you send Enter anyway, you get a denial, so skipping this step makes things actively worse.
if "Computer Use wants to control these apps" in text and "Enter to confirm" in text:
now = time.time()
if now - last_sent.get(tty, 0) < 8:
continue
if not selected_is_allow(text):
log(f"{tty}: dialog visible but cursor not on Allow; skipping")
last_sent[tty] = now
continue
res = press_enter(tty)
The way Enter is sent is also designed so it doesn't steal focus.
def press_enter(tty):
scpt = f'''
tell application "Terminal"
repeat with w in windows
repeat with t in tabs of w
if tty of t is "{tty}" then
do script "" in t
return "sent"
end if
end repeat
end repeat
return "notfound"
end tell'''
do script "" in t just writes a single newline byte to that tab's tty; it doesn't activate the window. Sends are deduplicated per tty with an 8-second window, and fcntl.flock prevents multiple instances from running.
The trap I fell into: launching from launchd hangs silently
My first attempt was to launch it from a recurring launchd job. The result: AppleEvents from python3 to Terminal hang silently under TCC (Automation). No prompt, nothing. It just freezes. Here's the actual log.
2026-09-12 15:32:35 osascript timeout
2026-09-12 15:32:57 osascript timeout
2026-09-12 15:33:19 osascript timeout
2026-09-12 15:33:41 osascript timeout
2026-09-12 15:34:03 osascript timeout
2026-09-12 15:34:25 osascript timeout
2026-09-12 15:34:47 osascript timeout
2026-09-12 15:35:09 osascript timeout
2026-09-12 15:35:31 osascript timeout
You can see osascript timeout repeating endlessly at 22-second intervals. A process launched from launchd sits outside the user's TCC context, so its control request to Terminal can't even surface an approval dialog and gets swallowed whole.
Note: TCC (Automation) is nastier than "a permission dialog appears and gets denied": it hangs silently without ever showing a prompt. If you're sending AppleEvents via launchd, suspecting this failure mode is the fastest route to a fix.
The workaround: launch as a child process of Terminal
The workaround was to launch it as a child process of Terminal itself. AppleEvents from a child process of the same app don't trigger a TCC permission request at all. Here's what's at the end of my ~/.zshrc.
# Claude Code computer-use「Allow for this session」を自動Enter(Terminal起点で起動=TCC不要・launchd起点はTCCで固まる)
if [[ "$TERM_PROGRAM" == "Apple_Terminal" ]] && ! pgrep -qf cu_dialog_autoallow.py; then
(nohup /usr/bin/python3 ~/.claude/scripts/cu_dialog_autoallow.py >/dev/null 2>&1 &)
fi
TERM_PROGRAM restricts this to shells started from Apple Terminal, pgrep stops a second instance, and nohup keeps it resident in the background. This block runs every time you open a new tab, but thanks to the pgrep guard only one process ever actually starts. After the switch, start pid=... shows up in the log and \r is actually being delivered to the dialog.
2026-09-12 15:36:16 /dev/ttys010: CU dialog -> Enter (sent)
2026-09-12 15:36:18 /dev/ttys011: CU dialog -> Enter (sent)
2026-09-12 15:36:26 /dev/ttys010: CU dialog -> Enter (sent)
Pitfalls I hit
-
Launched from launchd, AppleEvents hang silently under TCC → launch as a child of Terminal via
nohupat the end of.zshrc -
The
historyproperty is heavy at 600KB per tab → read only the visible screen withcontents of tab i of w -
repeat with t in tabs; contents of treturns the tab itself → use explicit index access withtab i of w -
Sending Enter without checking the cursor position misfires (you press it on the Deny side too) → parse the
❯pointer line and send only when Allow is confirmed -
Transient AppleScript connection drops and syntax errors show up in the log (
実行エラー: 接続が無効です (-609)/syntax error ... (-2741)) → design the loop to ignore one-off timeouts and keep polling (a lost cycle is retried 2 seconds later)
That last point is backed by real logs. On the morning of 09-13 and around midday on 09-16, connection drops and syntax errors came in bursts, but every one of them recovered on its own in the restart cycle a few minutes later, and an actual dialog approval (CU dialog -> Enter (sent)) was also recorded at 23:30 on 09-16. One caveat: I haven't yet measured \r reaching a real dialog at the moment another session is holding the CU lock; the end-to-end confirmation so far is against a fake dialog screen.
Summary
- Per the
stringsinvestigation, there is no settings-based route to make Computer Use approval permanent (session memory only; the PermissionRequest hook doesn't fire either) - The workaround is a design that polls the visible screen and checks the cursor position to press Enter on your behalf without misfiring
- Launching from launchd hangs silently under TCC. Running as a child process of Terminal is the only workaround
- One-off connection drops and syntax errors are swallowed by the polling loop and self-heal
Next time I'll write about a sibling of this AppleScript watcher daemon: tiling Terminal windows in one shot.
Have you found any other way to get past a TCC-gated AppleEvent from a background process without going through the app itself?
Written by **Lily* — I ship iOS apps and automate my content stack with Claude Code.
Follow along: Portfolio · X · GitHub*
Top comments (0)