#!/usr/bin/expect -f # Start Claude Code for an unattended run, answering the first-run gates. # # Claude Code has three one-time interactive prompts, and every one of them is a # silent, permanent hang for an agent with nobody at the keyboard — no error, no # log line, just a container that looks healthy and does nothing: # # 1. the theme picker (first run, or whenever the installed version is # newer than lastOnboardingVersion) # 2. "do you trust this folder?" (per workspace) # 3. the Bypass Permissions disclaimer (for --dangerously-skip-permissions) # # `seed-claude-config.py` pre-sets the config keys for 1 and 2. The disclaimer # has no such key — it is meant to be accepted by a person once — so it is # answered here instead. That is the honest reading of `sylph-agent loose`: the # operator accepted it by choosing to run this, and the container is exactly the # sandbox the warning asks for. # # ── Why the patterns are single words ── # Claude Code draws its UI with ABSOLUTE COLUMN escapes between words, so the # prompt arrives on the wire as # # 2.\x1b[8GYes,\x1b[13GI\x1b[15Gaccept # # A multi-word pattern like {Yes, I accept} therefore never matches, and the # wrapper sits there looking like it is not running at all. Match one word. set timeout 90 log_user 1 # Give the pty a wide, tall geometry. A detached `docker run -t` defaults to # 80x24, and Claude Code hard-wraps to the terminal width — which truncates the # Remote Control URL to "https://claude.ai/code/session_01…" in the one place # you need to read it, and makes `docker logs` nearly unusable generally. set stty_init "rows 50 cols 200" set answered_theme 0 set answered_trust 0 set answered_bypass 0 spawn -noecho claude --dangerously-skip-permissions {*}$argv expect { -re {Choose} { if {!$answered_theme} { set answered_theme 1; send "\r" } exp_continue } -re {trust} { if {!$answered_trust} { set answered_trust 1 send_user "\n\[claude-autonomous] accepting the workspace trust prompt\n" send "1\r" } exp_continue } -re {accept} { if {!$answered_bypass} { set answered_bypass 1 send_user "\n\[claude-autonomous] accepting the Bypass Permissions disclaimer\n" send "2\r" } exp_continue } timeout { # No new gate for a while: the session is up (or never had one). Stop # matching so nothing later in the run can be answered by accident. } eof { exit } } # Hand the terminal over for the rest of the run. interact