From 95ba2c70d587e761da7954ecc78d4ebdaebb1740 Mon Sep 17 00:00:00 2001 From: Time Attakc <89218912+time-attack@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:37:09 -0700 Subject: [PATCH] reland: fix(autopilot): export ~/.bun/bin onto PATH in cron wrapper (#2013) (#3305) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(autopilot): export ~/.bun/bin onto PATH in cron wrapper (#2013) The wrapper script that 'gbrain autopilot --install' writes to ~/.gbrain/autopilot-run.sh sources ~/.bashrc to inherit PATH for the exec'd gbrain binary (which has a '#!/usr/bin/env bun' shebang). The standard Debian/Ubuntu ~/.bashrc ships a non-interactive guard that returns early when bash is launched non-interactively (cron, launchd, systemd) — so PATH exports operators add to ~/.bashrc never reach the wrapper subprocess. The result: the wrapper dies silently with 'env: bun: No such file or directory', leaves a stale lockfile, and every subsequent cron tick hits the lockfile and bails. The nightly dream cycle hangs waiting on a worker that never comes back, and the wrapper's own 10-min stale-lock window is the only thing that can recover it. This bites every operator whose bashrc is the standard distro default (which is the default), and there is no warning at install time. Fix: prepend ~/.bun/bin to PATH directly in the wrapper, so it is self-contained regardless of which init file the OS loaded. Add a regression test alongside the existing zshenv/zshrc source-order test (v0.36.1.x #966) so this class of bug stays caught. * fix(test): scrub real agent-fork name from regression comment (privacy check) Co-Authored-By: Claude Fable 5 --------- Co-authored-by: klampatech <73077262+klampatech@users.noreply.github.com> Co-authored-by: Garry Tan Co-authored-by: Claude Fable 5 --- src/commands/autopilot.ts | 9 +++++++++ test/autopilot-install.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/commands/autopilot.ts b/src/commands/autopilot.ts index d43bd661f..ce4456e28 100644 --- a/src/commands/autopilot.ts +++ b/src/commands/autopilot.ts @@ -1318,6 +1318,15 @@ function writeWrapperScript(repoPath: string): string { # OPENAI/ANTHROPIC keys exported in zshenv reach autopilot. [ -f ~/.zshenv ] && source ~/.zshenv 2>/dev/null source ~/.zshrc 2>/dev/null || source ~/.bashrc 2>/dev/null || true +# Belt-and-suspenders PATH fix. ~/.bashrc ships with a non-interactive guard +# (\`case $- in *i*) ;; *) return;; esac\`) that exits early when launched from +# cron/systemd/launchd — so its PATH exports never reach this subprocess. +# Without bun on PATH, the exec'd gbrain (a \`#!/usr/bin/env bun\` script) fails +# silently with "env: bun: No such file or directory" and leaves a stale +# lockfile that blocks every subsequent tick. Prepending ~/.bun/bin here +# keeps the wrapper self-contained regardless of which init file the OS +# loaded. +export PATH="$HOME/.bun/bin:$PATH" exec '${safeGbrainPath}' autopilot --repo '${safeRepoPath}' `; writeFileSync(wrapperPath, wrapper, { mode: 0o755 }); diff --git a/test/autopilot-install.test.ts b/test/autopilot-install.test.ts index 023b03d7d..6b5954390 100644 --- a/test/autopilot-install.test.ts +++ b/test/autopilot-install.test.ts @@ -99,3 +99,29 @@ describe('autopilot wrapper script — env source order (v0.36.1.x #966)', () => expect(src).toMatch(/source\s+~\/\.zshrc/); }); }); + +// v0.42.x: the wrapper must export PATH with ~/.bun/bin before exec'ing +// gbrain. The exec'd gbrain has a `#!/usr/bin/env bun` shebang, and the +// standard Debian ~/.bashrc ships a non-interactive guard +// (`case $- in *i*) ;; *) return;; esac`) that exits early when cron/launchd/ +// systemd invokes bash non-interactively — so the PATH exports that +// operators put in ~/.bashrc never reach this subprocess. Without the +// explicit export the wrapper silently dies with `env: bun: No such file +// or directory`, leaves a stale lockfile, and blocks every subsequent tick +// for the 10-min stale-lock window. Regression: see a downstream agent +// fork's `cron doctor` reports — this caused a 1-week nightly-cycle outage +// on at least one operator machine before being diagnosed. +describe('autopilot wrapper script — bun PATH export (v0.42.x regression)', () => { + test('wrapper exports ~/.bun/bin onto PATH before the exec', async () => { + const { readFileSync } = await import('fs'); + const src = readFileSync('src/commands/autopilot.ts', 'utf8'); + // The export line must appear inside the writeWrapperScript heredoc. + expect(src).toMatch(/export\s+PATH="\$HOME\/\.bun\/bin:\$PATH"/); + // The export must precede the exec line, otherwise env never sees it. + const exportIdx = src.search(/export\s+PATH="\$HOME\/\.bun\/bin/); + const execIdx = src.search(/exec\s+'\${safeGbrainPath}'/); + expect(exportIdx).toBeGreaterThan(0); + expect(execIdx).toBeGreaterThan(0); + expect(exportIdx).toBeLessThan(execIdx); + }); +});