Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
23 changes: 23 additions & 0 deletions packages/git/src/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/git/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions packages/workflows/src/dag-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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;
Expand Down