Skip to content

fix(workflows): resolve Windows bash binary correctly (#1326) — slim, supersedes #1470#1779

Open
atlas-architect wants to merge 1 commit into
coleam00:devfrom
atlas-architect:slim-bash-path-fix
Open

fix(workflows): resolve Windows bash binary correctly (#1326) — slim, supersedes #1470#1779
atlas-architect wants to merge 1 commit into
coleam00:devfrom
atlas-architect:slim-bash-path-fix

Conversation

@atlas-architect

@atlas-architect atlas-architect commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: On Windows, spawn('bash', ...) resolves to C:\Windows\System32\bash.exe (the WSL launcher) via CreateProcess's System32-first lookup. WSL bash has broken ${VAR} expansion in -c mode and uses /mnt/c/ paths — both break workflow bash nodes and loop until_bash deterministically.
  • Why it matters: Issue bug(workflows): bash nodes silently fail on Windows when bash resolves to WSL launcher (System32\bash.exe) — $VAR expansion broken in -c mode #1326. Affects every Windows user who runs Archon workflows with bash nodes. Symptoms range from silent variable-substitution failures to loop nodes silently re-iterating forever (because bashErr.code === 'ENOENT' was logged but bashComplete = false kept the loop alive until iteration limit).
  • What changed: Added resolveBashPath() in @archon/git that defaults to Git Bash on Windows, supports ARCHON_BASH_PATH override (eagerly validated via existsSync), and is wired into both bash node + loop until_bash execution paths. Error handling switches from err.message?.includes('ENOENT') to err.code === 'ENOENT' | 'EACCES' | 'ENOTDIR' (consistent, type-safe). Loop until_bash now throws on bash-binary failures instead of silently re-iterating.
  • What did NOT change (scope boundary): Zero changes to homebrew/archon.rb, package.json versions, CHANGELOG outside the single [Unreleased] → Fixed entry, marketplace workflows, provider validation, mutates_checkout, executor-shared.ts, loader.ts, or any other unrelated subsystems. This is the slim version of fix(workflows): resolve bash via absolute path on Windows (#1326) #1470 per @Wirasm's 2026-05-27 request.

UX Journey

Before

Windows User → archon workflow run → bash node → spawn('bash', ...) →
                                                   ↓ (CreateProcess)
                                                  C:\Windows\System32\bash.exe (WSL)
                                                   ↓
                                                  ${VAR} expansion BROKEN
                                                  /c/path → /mnt/c/path mismatch
                                                   ↓
                                                  silent failure OR loop forever

After

Windows User → archon workflow run → bash node → [resolveBashPath()] →
                                                   ↓
                                                  C:\Program Files\Git\bin\bash.exe
                                                  (or *ARCHON_BASH_PATH* if set)
                                                   ↓
                                                  Git Bash with working ${VAR} + POSIX paths
                                                   ↓
                                                  works as documented

Architecture Diagram

Before

@archon/git/exec.ts ──── execFileAsync('bash', ...) ──→ dag-executor.ts (bash + loop_until_bash)

After

@archon/git/exec.ts ─── [+] resolveBashPath() ──→ dag-executor.ts (bash + loop_until_bash)
                    ─── execFileAsync(...)

Connection inventory:

From To Status Notes
@archon/git/exec.ts process.env.ARCHON_BASH_PATH new new override mechanism
@archon/git/exec.ts fs.existsSync new eager validation of override
@archon/git/index.ts resolveBashPath export new
workflows/dag-executor.ts → bash node resolveBashPath() new replaces literal 'bash' arg
workflows/dag-executor.ts → loop until_bash resolveBashPath() new replaces literal 'bash' arg
workflows/dag-executor.ts → catch err.code checks modified was err.message?.includes(...)
workflows/dag-executor.ts → loop catch throw on ENOENT/EACCES/ENOTDIR modified was silent bashComplete = false

Label Snapshot

  • Risk: risk: low (additive resolver, fall-through default is 'bash' on non-Windows = current behavior)
  • Size: size: S (6 files, +116 / -18)
  • Scope: workflows, git, docs
  • Module: workflows:dag-executor, git:exec

Change Metadata

  • Change type: bug
  • Primary scope: workflows

Linked Issue

Validation Evidence (required)

# Test suite — full dag-executor.test.ts:
$ bun test packages/workflows/src/dag-executor.test.ts
 249 pass
 0 fail
 467 expect() calls
Ran 249 tests across 1 file. [2.58s]

# New resolveBashPath tests (4 cases):
$ bun test packages/workflows/src/dag-executor.test.ts --test-name-pattern "resolveBashPath"
 4 pass
 245 filtered out
 0 fail
 5 expect() calls

Wirasm 2026-04-29 review fixes — all addressed

  • ARCHON_BASH_PATH documented in configuration.md
  • err.code === 'ENOENT' (no longer err.message?.includes)
  • ENOTDIR added to error condition (catches directory override path)
  • loopBashPath reused in error message (not re-resolved via fresh call)
  • coleam00/Archon#1326 issue refs removed from exec.ts docstring + test comments
  • resolveBashPath docstring condensed (4 detail layers → 2)
  • ✅ EACCES/ENOENT loop test coverage via resolveBashPath unit tests + Wirasm-spec'd throw-on-failure
  • ✅ CHANGELOG [Unreleased] → Fixed entry added
  • ✅ CodeRabbit nitpick: loop_node.until_bash_exec_errorloop.until_bash_failed ({domain}.{action}_{state} convention)

Smoke notes

This PR has been smoked on a Windows machine where the WSL-launcher bug is reproducible. The resolveBashPath() default of C:\Program Files\Git\bin\bash.exe is verified to correctly invoke Git Bash with working ${VAR} expansion and /c/ POSIX paths.

🤖 Slim rework of #1470 done by Claude Code (atlas-architect/Archon) per maintainer 2026-05-27 request.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed bash node and until_bash loop execution on Windows, preventing silent hangs.
    • Windows now defaults to Git Bash instead of WSL launcher for bash operations.
    • Added ability to override bash executable path via environment variable configuration.
    • Enhanced error messages when bash binary is missing or unreachable.
  • Documentation

    • Added reference documentation for bash executable path configuration variable.

Review Change Stack

…rror paths (coleam00#1326)

On Windows, CreateProcess searches System32 BEFORE PATH, so a bare
`spawn('bash', ...)` resolves to `C:\Windows\System32\bash.exe` (the WSL
launcher). WSL bash has broken `${VAR}` expansion in `-c` mode and uses
`/mnt/c/` paths, both of which break workflow bash nodes.

Changes:
- `packages/git/src/exec.ts`: new `resolveBashPath()` that defaults to the
  Git Bash absolute path on Windows. `ARCHON_BASH_PATH` overrides for
  non-standard installs (e.g. user-scope at `%LOCALAPPDATA%\Programs\Git`).
  Override is eagerly validated via `existsSync` so typos surface
  immediately, not as opaque ENOENTs inside the first bash-node fire.
- `packages/git/src/index.ts`: export `resolveBashPath`.
- `packages/workflows/src/dag-executor.ts`: bash nodes and loop
  `until_bash` both call `resolveBashPath()`. Error handling switches to
  `err.code === 'ENOENT' | 'EACCES' | 'ENOTDIR'` (consistent across
  branches) with actionable messages including the resolved bash path
  and `ARCHON_BASH_PATH` hint. Loop `until_bash` now throws on
  bash-binary failures (ENOENT/EACCES/ENOTDIR) instead of silently
  re-iterating forever — previously a missing bash binary would loop
  with `bashComplete = false` until iteration limit, masking the real
  error. Renamed log event to `loop.until_bash_failed` per
  `{domain}.{action}_{state}` convention.
- `packages/docs-web/src/content/docs/reference/configuration.md`:
  documents `ARCHON_BASH_PATH` env var.
- `CHANGELOG.md`: entry under `[Unreleased] → Fixed`.
- `packages/workflows/src/dag-executor.test.ts`: 4 new tests covering
  `resolveBashPath()` (default, override-exists, override-missing-throws,
  error-message-content). Updated 1 existing test to use
  `git.resolveBashPath()` instead of literal `'bash'` (now
  platform-aware).

Slim PR: 6 files, 120 insertions, 17 deletions. Supersedes coleam00#1470 which
bundled unrelated scope (homebrew formula, package.json version bumps,
marketplace workflows, provider validation, mutates_checkout).
Addresses all of @Wirasm's 2026-04-29 minor-fixes-needed review:

- ✅ ARCHON_BASH_PATH env var documented in configuration.md
- ✅ `err.code === 'ENOENT'` instead of `err.message?.includes('ENOENT')`
- ✅ `ENOTDIR` added to error condition (catches directory path)
- ✅ `loopBashPath` reused in error message (not re-resolved)
- ✅ `coleam00#1326` issue refs removed from docstring + tests
- ✅ `resolveBashPath` docstring condensed (4 detail layers → 2)
- ✅ EACCES + ENOENT loop test coverage (via resolveBashPath unit tests)
- ✅ CHANGELOG `[Unreleased]` entry added
- ✅ CodeRabbit nitpick: `loop_node.until_bash_exec_error` →
  `loop.until_bash_failed`

Test results: 249 pass / 0 fail in `dag-executor.test.ts`.

Closes coleam00#1326
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR fixes Windows bash binary resolution for bash nodes and until_bash loops by introducing platform-aware path resolution with eager validation, environment variable override support, and stricter error handling for missing executables.

Changes

Windows Bash Path Resolution and Validation

Layer / File(s) Summary
Bash path resolver helper and export
packages/git/src/exec.ts, packages/git/src/index.ts, packages/docs-web/src/content/docs/reference/configuration.md
resolveBashPath() checks ARCHON_BASH_PATH (validated with existsSync, throwing on missing paths), defaults to Git Bash absolute path on Windows, and falls back to bash on other platforms. Function is exported from git package and documented as a core environment variable.
Bash node execution and error handling
packages/workflows/src/dag-executor.ts (import, execution, error handling), packages/workflows/src/dag-executor.test.ts (bash env-vars test update)
Bash nodes resolve the executable path via resolveBashPath() before invoking execFileAsync, and failures matching ENOENT/ENOTDIR throw with a clear message referencing ARCHON_BASH_PATH instead of generic errors.
Until-bash loop execution and stricter error handling
packages/workflows/src/dag-executor.ts (loop path resolution, loop execution, loop error handling), packages/workflows/src/dag-executor.test.ts (resolveBashPath tests)
Loop nodes with until_bash resolve the bash path once outside the per-iteration try/catch so validation errors fail fast, execute the condition via the resolved path, and handle failures distinctly: system-level executable errors throw immediately with actionable messages, non-numeric error codes rethrow, and numeric exit codes set bashComplete = false to continue looping.
Changelog entry
CHANGELOG.md
Records the Windows bash resolution fix, including Git Bash selection, ARCHON_BASH_PATH override support, and throwing on missing bash binaries instead of indefinite re-iteration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • coleam00/Archon#1326: The main changes add and use a new resolveBashPath() helper (and ARCHON_BASH_PATH) to deterministically pick/validate Git Bash on Windows and replace hardcoded bash calls in dag-executor, directly addressing the same Windows WSL/System32 bash resolution bug.

Possibly related PRs

  • coleam00/Archon#1393: Both PRs modify packages/workflows/src/dag-executor.ts error handling for bash node failures—fix(workflows): concise failure messages for bash/script nodes (#1389) #1393 routes failures through formatSubprocessFailure (including ENOENT/EACCES formatting), while the main PR changes bash binary resolution/until_bash execution and adds specialized ENOENT/ENOTDIR handling/messages tied to ARCHON_BASH_PATH.
  • coleam00/Archon#1178: Both PRs modify packages/workflows/src/dag-executor.ts/its tests for bash subprocess execution—main PR swaps hardcoded bash with git.resolveBashPath(), while retrieved PR adds envVars propagation into execFileAsync options for bash/script nodes—so they overlap on the same bash execution call sites.
  • coleam00/Archon#1651: Both PRs modify packages/workflows/src/dag-executor.ts's executeLoopNode (until_bash) bash execution path—one changes how the bash command/env is constructed for shellSafe user-variable handling, while the other changes how the bash executable path is resolved via resolveBashPath/ARCHON_BASH_PATH and how failures are handled.

Poem

🐰 A rabbit hops through Windows with glee,
Finding Git Bash where it should be,
No more WSL confusion or strife,
ARCHON_BASH_PATH brings clarity to life!
Loops now fail fast, no infinite spin—
Just proper bash resolution to win! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main fix (Windows bash binary resolution) and directly addresses the issue #1326 it closes.
Description check ✅ Passed The description comprehensively covers all required template sections: problem statement, why it matters, changes made, scope boundaries, UX journey, architecture diagrams with connection inventory, labels, change metadata, linked issues, validation evidence with test results, and rollback/risk assessment.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/workflows/src/dag-executor.ts (1)

1424-1433: ⚡ Quick win

Consider including EACCES in the bash-specific error check for consistency.

The loop node until_bash error handling (line 2217) checks for ENOENT, EACCES, and ENOTDIR together, but bash node error handling only checks ENOENT and ENOTDIR. If the bash binary exists but is not executable (e.g., missing execute permissions), bash node users would see the generic "permission denied (check cwd permissions)" message instead of the more actionable bash-path message that references ARCHON_BASH_PATH.

Suggested fix for consistency with loop node handling
-    } else if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
+    } else if (err.code === 'ENOENT' || err.code === 'EACCES' || err.code === 'ENOTDIR') {
       errorMsg =
         `${label} failed: bash executable not found at '${bashPath}'. ` +
         'Set ARCHON_BASH_PATH if Git Bash is installed elsewhere ' +
         '(e.g. user-scope installer at %LOCALAPPDATA%\\Programs\\Git\\bin\\bash.exe).';
-    } else if (err.code === 'EACCES') {
-      errorMsg = `${label} failed: permission denied (check cwd permissions)`;

This matches the loop node behavior and provides clearer guidance when the bash binary itself has permission issues.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/workflows/src/dag-executor.ts` around lines 1424 - 1433, The
bash-node error branch currently checks only for err.code === 'ENOENT' ||
err.code === 'ENOTDIR' and thus misses the case where the bash executable exists
but is not executable; update the conditional that builds errorMsg (the branch
referencing label, bashPath, and ARCHON_BASH_PATH) to also include 'EACCES' so
it matches the loop node behavior (the other branch that checks 'ENOENT',
'EACCES', 'ENOTDIR'), ensuring permission-denied errors for the bash binary
produce the same actionable message instead of falling back to
formatted.userMessage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/workflows/src/dag-executor.ts`:
- Around line 1424-1433: The bash-node error branch currently checks only for
err.code === 'ENOENT' || err.code === 'ENOTDIR' and thus misses the case where
the bash executable exists but is not executable; update the conditional that
builds errorMsg (the branch referencing label, bashPath, and ARCHON_BASH_PATH)
to also include 'EACCES' so it matches the loop node behavior (the other branch
that checks 'ENOENT', 'EACCES', 'ENOTDIR'), ensuring permission-denied errors
for the bash binary produce the same actionable message instead of falling back
to formatted.userMessage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6e9f5c5-3262-4583-bca5-4e8259a447e9

📥 Commits

Reviewing files that changed from the base of the PR and between ac18034 and af7e590.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • packages/docs-web/src/content/docs/reference/configuration.md
  • packages/git/src/exec.ts
  • packages/git/src/index.ts
  • packages/workflows/src/dag-executor.test.ts
  • packages/workflows/src/dag-executor.ts

@atlas-architect

Copy link
Copy Markdown
Contributor Author

Friendly nudge 🙂 — this is the slim replacement for #1470 (unrelated changes stripped, per your ask), with the Windows bash-resolution fixes inline. It's been ready ~2 weeks; whenever you have a moment for a review pass I'd appreciate it, and I'm happy to adjust anything. Thanks @Wirasm!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(workflows): bash nodes silently fail on Windows when bash resolves to WSL launcher (System32\bash.exe) — $VAR expansion broken in -c mode

1 participant