Skip to content

Windows: bun run build aborts — package.json build script uses redirection constructs bunsh rejects; regression test only checks statically #1602

@osouthgate

Description

@osouthgate

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):

  1. Redirection on a subshell is unsupported. ( … ) > f → bunsh's own message: Subshells with redirections are currently not supported. Please open a GitHub issue.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions