From 900a619f310bd3d15f239962b2a758b6710c07bc Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 27 Apr 2026 23:57:13 -0700 Subject: [PATCH] fix(windows-ci): platform-aware claude-bin test + curate bin/ shebang spawns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 3 of windows-free-tests fixes. Round 2 (LF gitattributes + server-node.mjs build) cleared shard 1 entirely (skill-collision-sentinel and tab-isolation green). Shard 2 surfaced two more issues: 1. browse/test/claude-bin.test.ts:50 — the "PATH-resolvable override" test creates a fake binary 'fake-claude-cli' (no extension) and expects Bun.which to find it. On Windows, Bun.which probes PATHEXT extensions (.cmd, .exe, .bat) — a bare-name file is not discoverable. Production behavior is correct; the test was Mac/Linux-shaped. Fix: branch on process.platform. On Windows, write 'fake-claude-cli.cmd' with a Windows batch payload instead of a POSIX shebang script. 2. test/gstack-question-log.test.ts (and 18 sibling tests) — spawn a bash shebang script via spawnSync(BIN, args). Git Bash on Windows can run `bash /path/to/script` but spawnSync invokes CreateProcess directly, which doesn't parse #!/usr/bin/env bash. All these tests are Windows-fragile and can't run as-is. Fix: extend WINDOWS_FRAGILE_PATTERNS with `path.join(.., 'bin', ..)` detector. Curates 19 additional tests (benchmark-cli, brain-sync, builder-profile, explain-level-config, gbrain-*, gstack-question-*, hook-scripts, learnings, plan-tune, review-log, secret-sink-harness, taste-engine, telemetry, timeline, uninstall). Curated Windows subset: 95 → 76 tests (~59% of free suite). Still meaningful Windows coverage. The 52 excluded tests are tracked as a follow-up TODO for full Windows parity (shebang-bin spawns + POSIX file modes + raw /tmp/ etc). Co-Authored-By: Claude Opus 4.7 (1M context) --- browse/test/claude-bin.test.ts | 10 +++++++--- scripts/test-free-shards.ts | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/browse/test/claude-bin.test.ts b/browse/test/claude-bin.test.ts index 25aee32c..0b9d7eb9 100644 --- a/browse/test/claude-bin.test.ts +++ b/browse/test/claude-bin.test.ts @@ -40,10 +40,14 @@ describe('claude-bin', () => { test('PATH-resolvable override goes through Bun.which (the bug the fork shipped)', () => { // Make a fake binary in a temp dir, point PATH at it, set override to bare command name. + // Windows requires the file to have a PATHEXT-listed extension to be discoverable + // via Bun.which — without the extension Bun.which returns undefined. const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-bin-test-')); - const fakeBin = path.join(tmpDir, 'fake-claude-cli'); - fs.writeFileSync(fakeBin, '#!/bin/sh\necho fake\n'); - fs.chmodSync(fakeBin, 0o755); + const isWindows = process.platform === 'win32'; + const fakeBinName = isWindows ? 'fake-claude-cli.cmd' : 'fake-claude-cli'; + const fakeBin = path.join(tmpDir, fakeBinName); + fs.writeFileSync(fakeBin, isWindows ? '@echo fake\r\n' : '#!/bin/sh\necho fake\n'); + if (!isWindows) fs.chmodSync(fakeBin, 0o755); try { const got = resolveClaudeCommand({ PATH: tmpDir, diff --git a/scripts/test-free-shards.ts b/scripts/test-free-shards.ts index ed1a71c2..6ad9e1da 100755 --- a/scripts/test-free-shards.ts +++ b/scripts/test-free-shards.ts @@ -63,6 +63,11 @@ const WINDOWS_FRAGILE_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [ { pattern: /\.mode\s*&\s*0o[0-7]+/, reason: 'POSIX file mode bitmask (mode & 0o600 etc — Windows fakes mode bits)' }, { pattern: /\.endsWith\(['"]\//, reason: 'hardcoded forward-slash path assertion (Windows uses \\\\)' }, { pattern: /['"]\.\/[a-zA-Z][^"']*['"]\)\s*\.\s*toBe\(true\)/, reason: 'forward-slash path comparison' }, + // Tests that spawn a bash shebang script in bin/ via spawnSync. Git Bash on + // Windows can run `bash /path/to/script` but spawnSync(scriptPath, ...) + // tries to execute the file directly via CreateProcess, which fails on the + // shebang. Catches gstack-question-log.test.ts, gstack-paths.test.ts, etc. + { pattern: /path\.join\([^)]*,\s*['"]bin['"]\s*[,)]/, reason: 'spawns bin/ shebang script (Windows CreateProcess does not parse shebangs)' }, ]; export const DEFAULT_SHARD_COUNT = 20;