From ff1dbf901e323013dccfa2521ef037dde83bd529 Mon Sep 17 00:00:00 2001 From: Roberto Cano <3525807+robercano@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:39:16 +0200 Subject: [PATCH] fix(loop): pin transient driver units to the repo root via --working-directory A systemd-run --user transient unit defaults WorkingDirectory to $HOME, so every driver spawned via issue #119's transient-unit path started outside the repo, never loaded .claude/settings.local.json's bypassPermissions profile, and died phantom on its first gh call ("requires approval", no approver headless). Went live at the 2026-07-16 08:38Z re-arm and burned all 5 of issue #97's attempts in ~1h before the #95 attempt budget parked the loop. Scenario 11 now asserts the flag is passed with the resolved repo root. Co-Authored-By: Claude Fable 5 --- .claude/scripts/loop-daemon.sh | 9 +++++++++ .claude/scripts/loop-daemon.test.sh | 2 ++ 2 files changed, 11 insertions(+) diff --git a/.claude/scripts/loop-daemon.sh b/.claude/scripts/loop-daemon.sh index bc1f7ff..7022f39 100644 --- a/.claude/scripts/loop-daemon.sh +++ b/.claude/scripts/loop-daemon.sh @@ -531,8 +531,17 @@ run_driver() { # running/lingering). Both failure modes otherwise look identical: some # nonzero rc, no driver output, no other signal to tell them apart. local start_marker; start_marker="$(mktemp -u "$state_dir/.driver-started.XXXXXX")" + # --working-directory is NOT optional: a transient --user unit defaults its + # WorkingDirectory to $HOME, and a driver started there never loads the + # repo's .claude/settings.local.json (bypassPermissions) — every gh call + # then dies on "requires approval" with no approver in headless mode, and + # the driver exits phantom. Burned 5 attempts on issue #97 (2026-07-16) + # before this line existed. The setsid fallback below doesn't need it: a + # plain child inherits the daemon's own cwd (the repo, per the service + # unit's WorkingDirectory). systemd-run --user --wait --collect --quiet \ --unit="$unit" \ + --working-directory="$root" \ -p "RuntimeMaxSec=$timeout_dur" \ --setenv="CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=$CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS" \ --setenv="PATH=$PATH" \ diff --git a/.claude/scripts/loop-daemon.test.sh b/.claude/scripts/loop-daemon.test.sh index 5e0cbbe..7f0e0e4 100644 --- a/.claude/scripts/loop-daemon.test.sh +++ b/.claude/scripts/loop-daemon.test.sh @@ -619,6 +619,8 @@ check "scenario 11: RuntimeMaxSec threaded from LOOP_DRIVER_TIMEOUT=45m" bash -c grep -qF -- "RuntimeMaxSec=45m" "$1"' _ "$dir11/systemd-run.args" check "scenario 11: CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS passed via --setenv (issue #111 pt 4 preserved)" bash -c ' grep -qF -- "--setenv=CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0" "$1"' _ "$dir11/systemd-run.args" +check "scenario 11: --working-directory pins the transient unit to the repo root (not \$HOME)" bash -c ' + grep -qF -- "--working-directory=$2" "$1"' _ "$dir11/systemd-run.args" "$dir11" check "scenario 11: claude stub was invoked (under the transient unit)" [ -f "$dir11/claude.marker" ] check "scenario 11: claude stub received the prompt text" bash -c 'grep -qF "issue #200" "$1"' _ "$dir11/claude.marker" check "scenario 11: setsid (fallback path) was NOT invoked" [ ! -f "$dir11/setsid.marker" ]