Skip to content

Commit b3cb9a5

Browse files
committed
fix(windows): hasAuth cross-platform + findClaudePath prefer .cmd/.exe
Two more production fixes surfaced during the full native-Windows E2E (not the deterministic-only smoke): the previous pass verified unit tests + deterministic setup but hadn't installed Claude Code natively on Windows to run a real LLM-backed setup. 1. src/cli.ts hasAuth(): split PATH with a hardcoded ':' separator (POSIX-only — Windows uses ';') and looked for a bare-name file 'claude' (Windows executables need .exe/.cmd/.bat/.ps1 suffix). Result: hasAuth returned false on Windows even when Claude Code was installed via `npm install -g` and on PATH, bailing out of setup with "No Claude authentication found." Delegated to the existing findClaudePath() helper which already handles PATH.delimiter, PATHEXT suffixes, and standard-install locations cross-platform — no divergence between what hasAuth sees and what the scanners will try to use. 2. src/utils/agent-options.ts findClaudePath(): `where.exe claude` on Windows returns EVERY match from PATH including the bare-name shebang file that npm ships for Unix compatibility (e.g. C:\node\claude with no extension alongside C:\node\claude.cmd). Our prior code took the first line — the bare-name file — which the Claude Agent SDK / cmd.exe cannot execute. Now we filter the where.exe output to prefer entries ending in .cmd/.exe/.bat/.ps1 on Windows, falling back to the first entry if nothing matches. POSIX behavior unchanged. Linux: npm test 511/511 pass; full E2E on a real repo clone (tmp/axme-e2e, 79 .ts files) — setup LLM-scanned 28 decisions + 13 presets = 41 total, $0.92, 203s; Claude session with Bash + Read tools, SessionEnd hook fired, detached audit worker ran to audit_complete. No regression.
1 parent 116cb57 commit b3cb9a5

2 files changed

Lines changed: 21 additions & 12 deletions

File tree

src/cli.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,19 @@ function generateClaudeMd(projectPath: string, isWorkspace: boolean): void {
143143

144144
/**
145145
* Check if Claude auth is available.
146-
* Checks: ANTHROPIC_API_KEY, or claude binary exists on disk.
146+
* Checks: ANTHROPIC_API_KEY, or a locatable `claude` CLI (which is evidence
147+
* the user has either an API key or a signed-in Claude subscription).
148+
*
149+
* Delegates to findClaudePath() so the PATH-separator (":" vs ";"), executable
150+
* suffixes (".cmd", ".exe" on Windows), and standard-install locations are all
151+
* handled the same way scanner agents resolve the binary — no divergence.
147152
*/
148153
function hasAuth(): boolean {
149154
if (process.env.ANTHROPIC_API_KEY) return true;
150-
151-
// Check common claude binary locations directly (no shell needed)
152-
const { env } = process;
153-
const pathDirs = (env.PATH || "").split(":");
154-
for (const dir of pathDirs) {
155-
if (existsSync(join(dir, "claude"))) return true;
156-
}
157-
158-
return false;
155+
// findClaudePath already checks AXME_CLAUDE_EXECUTABLE, CLAUDE_CODE_ENTRYPOINT,
156+
// PATH lookup, standard install locations, and nvm dirs, cross-platform.
157+
const { findClaudePath } = require("./utils/agent-options.js") as typeof import("./utils/agent-options.js");
158+
return !!findClaudePath();
159159
}
160160

161161
/**

src/utils/agent-options.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,17 @@ export function findClaudePath(): string | undefined {
5555
try {
5656
const lookup = process.platform === "win32" ? "where.exe claude" : "which claude";
5757
const p = execSync(lookup, { encoding: "utf-8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }).trim();
58-
// `where` may return multiple lines (one per match); take the first.
59-
const first = p.split(/\r?\n/)[0].trim();
58+
const lines = p.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
59+
// On Windows, `where.exe claude` returns every matching entry including
60+
// the bare-name shebang file that npm ships for Unix compatibility
61+
// (e.g. C:\node\claude with no extension). cmd.exe / the Agent SDK
62+
// cannot execute such files — they need .cmd/.exe/.bat/.ps1. Pick the
63+
// first Windows-executable from the list; fall back to the first entry
64+
// only if nothing else matches (unlikely but keeps behaviour defined).
65+
const preferred = process.platform === "win32"
66+
? lines.find((r) => /\.(cmd|exe|bat|ps1)$/i.test(r))
67+
: undefined;
68+
const first = preferred ?? lines[0];
6069
if (first && existsSync(first)) {
6170
_claudePath = first;
6271
return _claudePath;

0 commit comments

Comments
 (0)