Skip to content

fix(windows): two bun-on-Windows blockers for browse build + runtime#1601

Open
w-khai wants to merge 2 commits into
garrytan:mainfrom
w-khai:windows-bun-fixes
Open

fix(windows): two bun-on-Windows blockers for browse build + runtime#1601
w-khai wants to merge 2 commits into
garrytan:mainfrom
w-khai:windows-bun-fixes

Conversation

@w-khai
Copy link
Copy Markdown

@w-khai w-khai commented May 19, 2026

What

Two independent fixes that together unblock gstack browse on Windows. Each commit is self-contained and can be cherry-picked individually.

Commit Touches Why
e0daa44 package.json (1 line) ./setup fails before binaries rebuild because bun's Windows shell doesn't support (cmd) > file subshell-with-redirect.
376c5cd browse/src/file-permissions.ts (+16/-1) Every browse invocation on Windows exits 1 with EEXIST because bun's fs.mkdirSync(p, {recursive:true}) throws when the dir exists (Node doesn't).

Without these two fixes, gstack browse is 100% unusable on Windows. With them, both ./setup and browse <cmd> work end-to-end on Windows 11 + bun 1.3.13 + git-bash + OneDrive home directory.

Reproduction (pre-patch)

$ cd ~/.claude/skills/gstack && ./setup
Building browse binary...
$ bun run vendor:xterm && ... && ( git rev-parse HEAD 2>/dev/null || true ) > browse/dist/.version && ...
error: Failed to run script build due to error Subshells with redirections are currently not supported. Please open a GitHub issue.

$ browse status
[browse] EEXIST: file already exists, mkdir 'C:\Users\...\.gstack'
$ echo $?
1

Verification (post-patch)

$ ./setup
[...]
[97ms]  bundle  7 modules
[604ms] compile  browse/dist/browse.exe
[...]
Node server bundle ready: .../browse/dist/server-node.mjs

$ browse status
[browse] Starting server...
Status: healthy
Mode: launched
URL: about:blank
Tabs: 1
$ echo $?
0

I drove a full real-app QA walkthrough end-to-end against an in-house SvelteKit + Go project on Windows after the fix landed — 12 browser-side commands across login, form fill, screenshot, JS eval, navigation — zero EEXIST recurrences.

Risk

Both fixes are POSIX no-ops:

  • The package.json change wraps subshell-with-redirect lines in bash -c '...'. Bash on macOS / Linux handles the wrapped form identically to the unwrapped one (it's just one extra fork). I ran bun run build after the patch on Windows; can't trivially exercise on macOS from this box but the syntax change is mechanical.
  • mkdirSecure only ADDS a catch arm for EEXIST (and re-throws everything else). Node's mkdirSync({recursive:true}) is documented to never throw EEXIST, so on Linux/macOS this catch arm never fires. The statSync + restrictDirectoryPermissions follow-through is idempotent.

Test plan

For maintainers wanting to reproduce:

  1. Windows 11 + git-bash + bun 1.3.13. Either install via official bun installer or use the version bundled with this repo's ./setup.
  2. Path with a space in it (OneDrive default works) — the space isn't strictly required to repro EEXIST but it's the realistic case.
  3. cd <some-project> (any dir is fine; the bug fires on the .gstack/ mkdir at browse startup).
  4. Apply master (pre-patch): ./setup fails on the build script. After commenting out the redirects locally, the resulting binary then fails with EEXIST on every browse invocation.
  5. Apply this PR: both ./setup and browse status complete cleanly.

Notes

  • Commits are split per-fix so you can land either one independently if you'd like more eyes on one of them.
  • Email on commits is the GitHub noreply form (212912583+w-khai@users.noreply.github.com) because my real email is private.
  • I'm happy to amend, split further, add CHANGELOG entries, or fold into a single commit if you prefer.

🤖 Generated with Claude Code

w-khai added 2 commits May 19, 2026 18:26
Bun's Windows shell parser rejects `(cmd) > file` with:

    error: Failed to run script build due to error
    Subshells with redirections are currently not supported.
    Please open a GitHub issue.

This makes `./setup` fail on every Windows install — the binaries
don't rebuild and the user is left with whatever stale browse.exe
was last committed (or no binary at all on a fresh clone).

Fix: wrap each `(git rev-parse HEAD ...) > path` and the trailing
`(rm -f ...)` cleanup in `bash -c '...'`. The version-stamp behavior
is identical (empty file when not in a git checkout vs SHA when we
are); the build now runs end-to-end on Windows via git-bash + bun
1.3.13.

Verified on Windows 11 + bun 1.3.13 + git-bash:
- Pre-patch: `./setup` aborts during `bun run build`.
- Post-patch: full build completes (~7s on a warm cache), all 5
  compiled exes land in their dist dirs, server-node.mjs bundles.

POSIX behaviour is unchanged — bash on macOS / Linux handles either
syntax. No risk to existing platforms.
Bun's compiled-binary `fs.mkdirSync(path, {recursive:true})` throws
EEXIST when the directory already exists. Node does NOT — `recursive:
true` is documented as suppressing EEXIST. The divergence makes every
`browse` invocation on Windows exit 1 with:

    [browse] EEXIST: file already exists, mkdir 'C:\...\.gstack'

because ensureStateDir() runs unconditionally at startup and the
project's `.gstack/` exists by then (gitignore'd state dir).

Reproduced on:
- Windows 11 Home (10.0.26200)
- bun 1.3.13
- OneDrive home directory (path contains a space)
- git-bash + plain cmd.exe — both surface the same EEXIST

A plain `node -e "require('fs').mkdirSync('.gstack', {recursive:true})"`
on the same machine + same path succeeds. So the bug is in bun's fs
shim, not in our code or the filesystem. But mkdirSecure can defend
defensively without waiting for an upstream bun fix.

Fix: catch EEXIST in mkdirSecure(), verify the existing path is in
fact a directory (statSync), then fall through to re-apply the ACL.
Throws on any other error code. The behavior on Node is unchanged
(EEXIST is never raised, so the catch never fires).

Verified end-to-end: rebuilt the browse binary with the patch,
re-ran `browse status` and `browse goto https://example.com` — both
return cleanly. Prior to the patch, both exited 1 with the EEXIST.

Without this fix, gstack browse is 100% unusable on Windows.
@jbetala7
Copy link
Copy Markdown
Contributor

Thanks for splitting this into two commits. One collision to call out: the package.json build-script part is now covered by #1594, which adds scripts/build.sh plus windows-setup-e2e and explicitly supersedes the Windows build-script cluster. The mkdirSecure/EEXIST part is distinct and may be worth preserving, but it is hard to evaluate while bundled with the already-covered build fix and there is no regression in browse/test/file-permissions.test.ts. Suggest rebasing after #1594/main and keeping this PR to the Windows mkdirSecure runtime fix, with a focused test or a linked issue for that separate failure.

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.

2 participants