From 53117bb8fafda70ba8195edb1504225e2440d3af Mon Sep 17 00:00:00 2001 From: Adam Herring Date: Tue, 21 Apr 2026 19:02:00 -0700 Subject: [PATCH 1/2] fix(workflows): resolve bash via absolute path on Windows (#1326) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1326. ## Problem On Windows, `child_process.execFile('bash', ...)` fails to use Git Bash even when Git Bash is first on PATH. Windows CreateProcess searches the System32 directory BEFORE consulting the PATH env var, so bare `'bash'` resolves to `C:\Windows\System32\bash.exe` — the WSL launcher. The WSL bash has two pathologies that break workflow bash nodes: 1. `${VAR}` expansion is broken when bash is invoked in `-c` mode via CreateProcess arg-passing. Variables emit as empty strings regardless of environment. 2. Path convention is `/mnt/c/...` rather than Git Bash's `/c/...`, so any absolute path substitutions (worktree paths, cwd, repo paths) mismatch expectations of downstream bash nodes. Symptom seen in A00 workflow: `precheck-worktree` node fails fast with TARGET empty, even though the token substitution correctly wrote TARGET= into the script. ## Fix Add `resolveBashPath()` helper in `@archon/git/exec` that returns: - `process.env.ARCHON_BASH_PATH` if set (escape hatch for non-standard Git Bash installs, e.g. user-scope installer at `%LOCALAPPDATA%\Programs\Git\bin\bash.exe`) - `C:\Program Files\Git\bin\bash.exe` on Windows (Git Bash default) - `bash` on Linux/macOS (unchanged PATH behavior) Update the two `execFileAsync('bash', ...)` call sites in `dag-executor.ts` (bash node + loop node until-bash check) to call through `resolveBashPath()`. ## Verification Local smoke test on Windows 11 + Git Bash 2.47: ``` Resolved bash: C:\Program Files\Git\bin\bash.exe TARGET_FROM_PARENT=test-value-123 ← ${VAR} expansion works PATH_CONVENTION=/c/Dev/platform/Archon/... ← /c/ convention preserved ``` Before the patch (bare `'bash'`), same test returned: ``` Resolved bash: bash TARGET_FROM_PARENT= ← WSL bash dropped the variable PATH_CONVENTION=/mnt/c/Dev/... ← WSL mount convention ``` Type-check green across all 9 packages. ## Compatibility - Linux/macOS: no behavioral change (still resolves `bash` via PATH). - Windows with standard Git for Windows install: works out of the box. - Windows with user-scope Git install: override via ARCHON_BASH_PATH. - Docker containers (Linux): no change needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/git/src/exec.ts | 23 +++++++++++++++++++++++ packages/git/src/index.ts | 2 +- packages/workflows/src/dag-executor.ts | 6 +++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/git/src/exec.ts b/packages/git/src/exec.ts index 9380e1e8b8..99a9ccb9bc 100644 --- a/packages/git/src/exec.ts +++ b/packages/git/src/exec.ts @@ -4,6 +4,29 @@ import { promisify } from 'util'; const promisifiedExecFile = promisify(execFile); +/** + * Resolve the bash binary path in a platform-aware way. + * + * On Windows, CreateProcess searches the System32 directory BEFORE the PATH + * env var. Bare `spawn('bash', ...)` therefore resolves to + * `C:\Windows\System32\bash.exe` (the WSL launcher), whose bash has broken + * `${VAR}` expansion when invoked in `-c` mode and uses `/mnt/c/` path + * convention instead of `/c/`. Both break workflow bash nodes. + * + * Fix: on Windows, default to the Git Bash absolute path. Overridable via + * ARCHON_BASH_PATH for non-standard Git installs (e.g. user-scope installer + * at %LOCALAPPDATA%\Programs\Git\bin\bash.exe). + * + * See: coleam00/Archon#1326 + */ +export function resolveBashPath(): string { + if (process.env.ARCHON_BASH_PATH) return process.env.ARCHON_BASH_PATH; + if (process.platform === 'win32') { + return 'C:\\Program Files\\Git\\bin\\bash.exe'; + } + return 'bash'; +} + /** Wrapper around child_process.execFile for test mockability */ export async function execFileAsync( cmd: string, diff --git a/packages/git/src/index.ts b/packages/git/src/index.ts index 8cfdc865f7..e426d8cf0a 100644 --- a/packages/git/src/index.ts +++ b/packages/git/src/index.ts @@ -11,7 +11,7 @@ export type { export { toRepoPath, toBranchName, toWorktreePath } from './types'; // Process and filesystem wrappers -export { execFileAsync, mkdirAsync } from './exec'; +export { execFileAsync, mkdirAsync, resolveBashPath } from './exec'; // Worktree operations export { diff --git a/packages/workflows/src/dag-executor.ts b/packages/workflows/src/dag-executor.ts index facfbd1068..9fe7788728 100644 --- a/packages/workflows/src/dag-executor.ts +++ b/packages/workflows/src/dag-executor.ts @@ -7,7 +7,7 @@ */ import { readFile } from 'fs/promises'; import { resolve, isAbsolute } from 'path'; -import { execFileAsync } from '@archon/git'; +import { execFileAsync, resolveBashPath } from '@archon/git'; import { discoverScripts } from './script-discovery'; import type { WorkflowAssistantOptions, @@ -1359,7 +1359,7 @@ async function executeBashNode( const timeout = node.timeout ?? SUBPROCESS_DEFAULT_TIMEOUT; try { - const { stdout, stderr } = await execFileAsync('bash', ['-c', finalScript], { + const { stdout, stderr } = await execFileAsync(resolveBashPath(), ['-c', finalScript], { cwd, timeout, }); @@ -2018,7 +2018,7 @@ async function executeLoopNode( nodeOutputs, true // escapedForBash ); - await execFileAsync('bash', ['-c', substitutedBash], { cwd }); + await execFileAsync(resolveBashPath(), ['-c', substitutedBash], { cwd }); bashComplete = true; // exit 0 = complete } catch (e) { const bashErr = e as NodeJS.ErrnoException; From c0427bf35aa2b01f29cd33eeb4f15bbccfa00050 Mon Sep 17 00:00:00 2001 From: Adam Herring Date: Wed, 22 Apr 2026 06:26:27 -0700 Subject: [PATCH 2/2] chore(local): gitignore Atlas Intelligence .env.template (not for upstream) --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a2f33c5d5c..86abee56a2 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ packages/server/.env skills-lock.json test-results/ .archon/ralph/ + +# Atlas Intelligence local — 1Password-backed .env template (not for upstream) +.env.template