Summary
On Windows, ./setup runs bun run build (setup:281), which makes bun's built-in shell (bunsh) interpret the build script in package.json:12. That script contains three:
( git rev-parse HEAD 2>/dev/null || true ) > <pkg>/dist/.version
bunsh does not support redirection on a grouping construct, so the build aborts with:
Subshells with redirections are currently not supported. Please open a GitHub issue.
Result on Windows: bun run build never completes, the compiled binaries (browse/dist/browse, bin/gstack-global-discover, etc.) stay stale across every /gstack-upgrade, and the only workaround is to build manually under Git Bash and re-run ./setup with NEEDS_BUILD=0.
This survived the v1.38.0.0 "fix" (brace-group → subshell) and the v1.39.1.0 follow-up because the regression test guards the wrong axis and never executes the script (details below).
Reproduction
// repro.mjs
import { $ } from "bun";
console.log("bun", Bun.version, process.platform);
try { await $`( echo H 2>/dev/null || true ) > t.txt`.quiet(); console.log("OK"); }
catch (e) { console.log("FAIL ->", String(e.message||e).split("\n")[0]); }
$ bun repro.mjs
bun 1.3.14 win32
FAIL -> Subshells with redirections are currently not supported. Please open a GitHub issue.
Confirmed at bun 1.3.4 and 1.3.14 on Windows 11 (win32). bun run build uses bunsh on Windows (no system sh), so package.json:12 hits this directly.
Root cause
The current form fails for two independent reasons, each backed by a tight bunsh error signature (bun 1.3.14 / win32):
- Redirection on a subshell is unsupported.
( … ) > f → bunsh's own message: Subshells with redirections are currently not supported. Please open a GitHub issue.
- More than one redirection on a simple command is unsupported.
cmd > f 2>/dev/null (either order) → expected a command or assignment but got: "Redirect". So even unwrapping the subshell to git rev-parse HEAD > f 2>/dev/null || true still fails.
The current ( git rev-parse HEAD 2>/dev/null || true ) > X/.version triggers both at once.
Full matrix at bun 1.3.14 / win32:
| Construct |
bunsh |
( cmd … ) > f (subshell + redirect — current) |
FAIL — "Subshells with redirections are currently not supported" |
cmd > f 2>/dev/null (two redirects, either order) |
FAIL — "expected a command or assignment but got: Redirect" |
cmd > f || true (single redirect + ||) |
OK |
cmd > f ; true (single redirect + ;) |
OK |
cmd 2>/dev/null || true (single redirect, no stdout) |
OK |
{ cmd …; } > f (brace group + redirect — pre-v1.38.0.0) |
also FAIL (exit 1, different signature — not load-bearing here) |
Note this means v1.38.0.0's brace-group → subshell move (PR #1460) and the test that enforces it never addressed the actual failure modes — the pre-v1.38.0.0 brace form was broken too, just differently.
Why the regression test never catches it
test/build-script-shell-compat.test.ts does only:
const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
// regex over PKG.scripts strings — asserts subshell, not brace group
It is a static string check on package.json. It never spawns bun run build, never invokes bunsh — it only distinguishes brace-group vs subshell. Even if brace groups happened to work, this test still could not catch either failure mode above, because it executes nothing. So it stays green while the build is broken on Windows, and it actively steered the code toward the subshell form (which is itself broken). v1.39.1.0 then "fixed a regression back to subshells", doubling down on a form that cannot work.
Suggested fixes
Option A — robust (recommended). package.json:12 already shells out: bash browse/scripts/build-node-server.sh. Move the three .version writes into that script (or a sibling bash scripts/write-version.sh) and invoke via bash. Real sh/bash has none of bunsh's grammar limits, so the exact original semantics — including 2>/dev/null stderr suppression — are preserved, and the line is permanently immune to bunsh grammar quirks.
Option B — minimal inline diff. Replace each occurrence with a single-redirect, no-grouping form:
- ( git rev-parse HEAD 2>/dev/null || true ) > browse/dist/.version
+ git rev-parse HEAD > browse/dist/.version || true
Empirically verified semantically equivalent in bunsh (bun 1.3.14 / win32):
- in a git checkout (gstack's real build path): writes the 40-char SHA — identical
- outside a git checkout: redirect pre-truncates → empty
.version, || true keeps the && chain alive, subsequent build steps run — identical outcome
- only delta: in a non-git build, git's
fatal: line goes to stderr instead of being swallowed by 2>/dev/null (cosmetic; never occurs for gstack since it is always built from a clone). Dropping 2>/dev/null is required — > f 2>/dev/null is two redirects, which bunsh also rejects.
Test hardening (the durable fix). build-script-shell-compat.test.ts should execute the version-write snippet through Bun.$ (or run bun run build in a tmp dir) and assert it exits 0, instead of regexing package.json. A static brace-vs-subshell check cannot catch this class of bug — only running it under bunsh can.
Environment
- bun 1.3.14 (also 1.3.4), Windows 11 (win32)
- gstack v1.40.0.0 (
026751ea)
- Trigger path:
setup:281 bun run build → bunsh → package.json:12
- The bunsh failure was observed by the reporter on Windows; bun's shell documentation lists subshell-with-redirection as not yet implemented.
Summary
On Windows,
./setuprunsbun run build(setup:281), which makes bun's built-in shell (bunsh) interpret thebuildscript inpackage.json:12. That script contains three:bunsh does not support redirection on a grouping construct, so the build aborts with:
Result on Windows:
bun run buildnever completes, the compiled binaries (browse/dist/browse,bin/gstack-global-discover, etc.) stay stale across every/gstack-upgrade, and the only workaround is to build manually under Git Bash and re-run./setupwithNEEDS_BUILD=0.This survived the v1.38.0.0 "fix" (brace-group → subshell) and the v1.39.1.0 follow-up because the regression test guards the wrong axis and never executes the script (details below).
Reproduction
Confirmed at bun 1.3.4 and 1.3.14 on Windows 11 (win32).
bun run builduses bunsh on Windows (no systemsh), sopackage.json:12hits this directly.Root cause
The current form fails for two independent reasons, each backed by a tight bunsh error signature (bun 1.3.14 / win32):
( … ) > f→ bunsh's own message:Subshells with redirections are currently not supported. Please open a GitHub issue.cmd > f 2>/dev/null(either order) →expected a command or assignment but got: "Redirect". So even unwrapping the subshell togit rev-parse HEAD > f 2>/dev/null || truestill fails.The current
( git rev-parse HEAD 2>/dev/null || true ) > X/.versiontriggers both at once.Full matrix at bun 1.3.14 / win32:
( cmd … ) > f(subshell + redirect — current)cmd > f 2>/dev/null(two redirects, either order)cmd > f || true(single redirect +||)cmd > f ; true(single redirect +;)cmd 2>/dev/null || true(single redirect, no stdout){ cmd …; } > f(brace group + redirect — pre-v1.38.0.0)Note this means v1.38.0.0's brace-group → subshell move (PR #1460) and the test that enforces it never addressed the actual failure modes — the pre-v1.38.0.0 brace form was broken too, just differently.
Why the regression test never catches it
test/build-script-shell-compat.test.tsdoes only:It is a static string check on
package.json. It never spawnsbun run build, never invokes bunsh — it only distinguishes brace-group vs subshell. Even if brace groups happened to work, this test still could not catch either failure mode above, because it executes nothing. So it stays green while the build is broken on Windows, and it actively steered the code toward the subshell form (which is itself broken). v1.39.1.0 then "fixed a regression back to subshells", doubling down on a form that cannot work.Suggested fixes
Option A — robust (recommended).
package.json:12already shells out:bash browse/scripts/build-node-server.sh. Move the three.versionwrites into that script (or a siblingbash scripts/write-version.sh) and invoke viabash. Realsh/bash has none of bunsh's grammar limits, so the exact original semantics — including2>/dev/nullstderr suppression — are preserved, and the line is permanently immune to bunsh grammar quirks.Option B — minimal inline diff. Replace each occurrence with a single-redirect, no-grouping form:
Empirically verified semantically equivalent in bunsh (bun 1.3.14 / win32):
.version,|| truekeeps the&&chain alive, subsequent build steps run — identical outcomefatal:line goes to stderr instead of being swallowed by2>/dev/null(cosmetic; never occurs for gstack since it is always built from a clone). Dropping2>/dev/nullis required —> f 2>/dev/nullis two redirects, which bunsh also rejects.Test hardening (the durable fix).
build-script-shell-compat.test.tsshould execute the version-write snippet throughBun.$(or runbun run buildin a tmp dir) and assert it exits 0, instead of regexingpackage.json. A static brace-vs-subshell check cannot catch this class of bug — only running it under bunsh can.Environment
026751ea)setup:281bun run build→ bunsh →package.json:12