Skip to content

fix(windows): .gitignore glob for .exe + find-browse .exe resolution#1554

Open
Mike-E-Log wants to merge 2 commits into
garrytan:mainfrom
Mike-E-Log:pr/windows-gaps
Open

fix(windows): .gitignore glob for .exe + find-browse .exe resolution#1554
Mike-E-Log wants to merge 2 commits into
garrytan:mainfrom
Mike-E-Log:pr/windows-gaps

Conversation

@Mike-E-Log
Copy link
Copy Markdown

Summary

Two small, narrow Windows-portability fixes that don't appear to be covered by any open PR:

  1. .gitignore — match the .exe suffix on bin/gstack-global-discover
  2. browse/src/find-browse.ts — resolve the .exe suffix on Windows, mirroring the helper already in make-pdf/src/browseClient.ts:89 and make-pdf/src/pdftotext.ts:52

Verified live on Windows 11 + Git Bash MSYS2 + bun 1.3.13. First-time contributor; please tell me if I've missed a convention.

Scoping note (deliberate omission)

I originally drafted this PR with a third fix for the bun-shell (...) > redirect bug in package.json's build script. On checking the PR list before opening, I found that ground is already heavily covered:

Five existing PRs is enough — adding a sixth would be noise rather than signal. I dropped that commit before opening this PR. The two fixes here are deliberately the ones that don't overlap with that crowd.

Why each fix exists

Fix 1: .gitignorebin/gstack-global-discover*

One line:

-bin/gstack-global-discover
+bin/gstack-global-discover*

Why: the pattern was exact-match. On Linux/macOS, bun --compile produces bin/gstack-global-discover (no extension) and the rule matches. On Windows, the output is bin/gstack-global-discover.exe and the rule misses it, so git status shows it as untracked after every build.

Precedent: browse/dist/ (line 4) uses a directory pattern, which catches every extension under the directory. Same conceptual move — make the rule cover both Linux and Windows compile outputs — applies here. The * glob is the minimal change.

Tighter alternatives (happy to switch if preferred): two explicit lines (bin/gstack-global-discover + bin/gstack-global-discover.exe). The glob form would also match hypothetical future siblings like .map or .dSYM; the tracked source bin/gstack-global-discover.ts is unaffected either way (it was added before the ignore pattern and remains tracked).

Fix 2: browse/src/find-browse.ts — Windows .exe resolution

~30 lines: added two local helpers (isExecutable and findExecutable) and routed both candidate path lookups through them.

function isExecutable(p: string): boolean {
  try {
    accessSync(p, constants.X_OK);
    return true;
  } catch {
    return false;
  }
}

function findExecutable(base: string): string | null {
  if (isExecutable(base)) return base;
  if (process.platform === 'win32') {
    for (const ext of ['.exe', '.cmd', '.bat']) {
      const withExt = base + ext;
      if (isExecutable(withExt)) return withExt;
    }
  }
  return null;
}

Why: find-browse was using existsSync('.../browse') (exact-match, no extension). On Windows, bun --compile produces browse.exe, and Node's fs.existsSync is strict (unlike Git Bash's test -x which auto-resolves .exe on MSYS2). The compiled find-browse.exe therefore always emits ERROR: browse binary not found. Run: cd <skill-dir> && ./setup on Windows, even when browse.exe is right next to it.

Precedent: exact mirror of the equivalent helpers in make-pdf/src/browseClient.ts:89-98 (findExecutable) + make-pdf/src/browseClient.ts:159-166 (isExecutable), and the same pair in make-pdf/src/pdftotext.ts:52-61 + :117-124. Same signature, same .exe / .cmd / .bat ordering, same win32 platform guard, same accessSync(constants.X_OK) probe. The comment in make-pdf/src/pdftotext.ts:52 explicitly documents the "duplicated rather than shared because the two modules already duplicate isExecutable for compile-isolation" choice — this third copy in find-browse keeps the module self-contained for the same reason.

Daily-impact note: no SKILL.md file currently invokes find-browse (verified via grep across all SKILL.md files at any depth). The /browse skill's discovery uses bash test -x which Git Bash auto-resolves to .exe on Windows. So this is a latent bug today: real defect, no current Windows consumer. But browse/bin/find-browse (the bash shim) does exec the compiled binary as its primary path, and BROWSER.md / CLAUDE.md document find-browse as the canonical lookup. Any Node consumer that calls it would fail on Windows today.

Test plan

  • bun test browse/test/find-browse.test.ts test/build-script-shell-compat.test.ts — 6 pass, 0 fail
  • ./browse/dist/find-browse.exe invoked from various working directories on Windows — returns the absolute path to browse.exe instead of erroring
  • After bun run build on Windows, git status no longer shows untracked bin/gstack-global-discover.exe
  • Linux/macOS behavior unchanged — findExecutable only takes the .exe branch on process.platform === 'win32'; the .gitignore glob still matches the no-extension form
  • Diff scope verified clean: two files, 35 insertions, 4 deletions

Out of scope

  • Build-script (...) > bun-shell incompatibility — see PRs listed above
  • Cross-module dedup of the findExecutable helper (two existing copies in make-pdf/; this third copy keeps the module self-contained, per the comment in pdftotext.ts:52)
  • Windows CI coverage for these specific gaps

Notes

  • First-time contributor here. Tell me if I've missed a convention.
  • Two commits, one per logical change, per the "always bisect commits" rule in CLAUDE.md.
  • Both commits trailer-credit Claude Opus 4.7 as co-author per the v1.39.2.0 precedent.

Mike-E-Log and others added 2 commits May 16, 2026 21:52
The current pattern `bin/gstack-global-discover` is exact-match and
catches the Linux/macOS compile output. On Windows, `bun build --compile`
appends `.exe`, producing `bin/gstack-global-discover.exe` which falls
through the gitignore and shows as untracked after every build.

Add the `*` glob suffix so the pattern catches both the no-extension form
(Linux/macOS) and the `.exe` form (Windows). Precedent: the same .gitignore
already uses `browse/dist/` (directory pattern) for the other compiled
binaries, which works on Windows for the same reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`browse/src/find-browse.ts` uses Node's `fs.accessSync(p, X_OK)` with the
hardcoded base name `browse` (no extension). On Windows, `bun build
--compile` produces `browse.exe`. Node's `accessSync` is exact-match and
on Windows degrades to a simple existence check (Windows has no true
execute bit). So the compiled `find-browse.exe` always errors on Windows
with `"ERROR: browse binary not found. Run: cd <skill-dir> && ./setup"`
even when `browse.exe` is right next to it. Git Bash's `test -x`
auto-resolves `.exe` on MSYS2, but Node's fs API does not.

Add a local `isExecutable(p)` helper and a `findExecutable(base)` helper
that probes the bare path then, on Windows, tries the `.exe / .cmd / .bat`
extension fallback. Replace each callsite in `locateBinary()` to use the
resolver.

Daily impact is currently low because no SKILL.md uses find-browse
(verified via grep across all `*/SKILL.md` files, top-level and nested) —
the /browse skill's inline bash discovery uses `test -x` which Git Bash
auto-resolves on Windows. But the compiled binary is shipped via setup and
externally documented in BROWSER.md / CLAUDE.md as the canonical lookup
path; any Node consumer that calls it would fail on Windows today.

Exact mirror of the equivalent helpers in
`make-pdf/src/browseClient.ts:89-98` (`findExecutable`) +
`make-pdf/src/browseClient.ts:159-166` (`isExecutable`), and the same pair
in `make-pdf/src/pdftotext.ts:52-61` + `:117-124`. Same signature, same
`.exe / .cmd / .bat` ordering, same `win32` platform guard, same
`accessSync(constants.X_OK)` probe. The gstack codebase has two existing
copies of this pattern (`browse/src` referenced earlier, plus make-pdf
twice); a third copy in find-browse keeps the module self-contained, per
the comment in `make-pdf/src/pdftotext.ts:52` explicitly documenting the
duplication-for-compile-isolation choice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shelman09 pushed a commit to Namleh-Studios/gstack that referenced this pull request May 19, 2026
…find-browse (garrytan#1554)

bun build --compile on Windows appends .exe to the output filename,
producing browse.exe instead of browse. find-browse's existsSync probe
only checked the bare path and returned null on Windows even when the
binary was correctly built. .gitignore similarly only excluded the
bare bin/gstack-global-discover path, leaving the .exe variant
tracked.

This commit:
- .gitignore: changes `bin/gstack-global-discover` →
  `bin/gstack-global-discover*` so the Windows .exe variant is ignored
- browse/src/find-browse.ts: adds isExecutable + findExecutable helpers
  that fall back to .exe/.cmd/.bat probing on Windows, mirroring the
  same helper already in make-pdf/src/browseClient.ts and pdftotext.ts

Contributed by @Mike-E-Log via garrytan#1554.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shelman09 pushed a commit to Namleh-Studios/gstack that referenced this pull request May 19, 2026
…ndows-latest

Adds .github/workflows/windows-setup-e2e.yml as the gate that catches
Bun shell-parser regressions in the build chain before they reach
users. Triggers on PRs touching package.json, scripts/build.sh,
scripts/write-version-files.sh, setup, browse cli/find-browse, or
gstack-paths.

What it verifies:
1. bun run build completes on Windows (the previously-broken path that
   garrytan#1538/garrytan#1537/garrytan#1530/garrytan#1457/garrytan#1561 reported)
2. All compiled binaries land on disk (browse.exe, find-browse.exe,
   design.exe, gstack-global-discover.exe)
3. find-browse resolves to the .exe variant on Windows (regression
   gate for garrytan#1554)
4. gstack-paths returns non-empty GSTACK_STATE_ROOT/PLAN_ROOT/TMP_ROOT
   on Windows (regression gate for garrytan#1570)

Complements the existing windows-free-tests.yml (curated unit subset);
this new workflow exercises the install path itself.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant