Skip to content

fix(make-pdf): Bun.which-based binary resolution for browse + pdftotext on Windows (v1.24 extension)#1307

Open
scarson wants to merge 1 commit intogarrytan:mainfrom
scarson:fix/windows-binary-resolution-v2
Open

fix(make-pdf): Bun.which-based binary resolution for browse + pdftotext on Windows (v1.24 extension)#1307
scarson wants to merge 1 commit intogarrytan:mainfrom
scarson:fix/windows-binary-resolution-v2

Conversation

@scarson
Copy link
Copy Markdown

@scarson scarson commented May 3, 2026

Summary

On Windows, make-pdf generate fails with browse binary not found even after a successful ./setup. The resolution logic in make-pdf/src/browseClient.ts probes bare paths, but bun build --compile --outfile browse/dist/browse emits browse/dist/browse.exe on Windows, so every probe misses. Same root cause in make-pdf/src/pdftotext.ts:resolvePdftotext.

This is a v1.24-aligned reshape of #1118 (closed in favor of this PR). v1.24.0.0 (#1252) introduced Bun.which() + GSTACK_*_BIN override pattern in browse/src/claude-bin.ts for the claude binary. This PR extends the same pattern to the two other binary resolvers in the codebase: resolveBrowseBin and resolvePdftotext.

Root cause

Two independent issues compound:

1. fs.constants.X_OK is degraded to existence-checking on Windows. Per the Node.js docs on fs.accessSync: fs.accessSync('/path/to/browse', X_OK) on Windows checks whether a file literally named browse (no extension) exists. When bun emits browse.exe, the check returns false. Bun.which() handles this correctly via PATHEXT.

2. bun --compile --outfile appends .exe on Windows unconditionally. The build command targets browse/dist/browse etc.; on Windows, bun silently appends .exe. Confirmed:

$ ls ~/.claude/skills/gstack/{browse,make-pdf,design}/dist/*.exe
browse/dist/browse.exe
make-pdf/dist/pdf.exe
design/dist/design.exe

3. The which browse PATH fallback doesn't help native Windows. which ships with Git Bash but not cmd.exe / PowerShell. Bun.which('browse') works from all three.

Coordination with #1094

@BkashJEE's PR #1094 (still open) covered browseClient.ts independently with a narrower scope (no pdftotext, no tests, no GSTACK_*_BIN naming). This PR's pdftotext fix + cross-platform tests + v1.24-aligned env override naming are additive. Either order of merge works; happy to rebase as a follow-on if #1094 lands first.

What changed from #1118

  • GSTACK_BROWSE_BIN / GSTACK_PDFTOTEXT_BIN as the v1.24-aligned overrides; BROWSE_BIN / PDFTOTEXT_BIN remain as back-compat aliases (matching v1.24's GSTACK_CLAUDE_BIN ?? CLAUDE_BIN pattern).
  • Bun.which() replaces execFileSync('which', ...) for PATH lookup. No more where-vs-which branch.
  • findExecutable(base) helper exported from each module — probes .exe/.cmd/.bat after the bare-path miss on win32. POSIX behavior bit-identical (isExecutable short-circuits before the win32 branch).
  • macCandidatesposixCandidates in pdftotext.ts (always was POSIX-only). No Windows candidates added — Poppler scatters across Scoop / Chocolatey / oschwartz10612-poppler-windows; guessing causes false positives. Set GSTACK_PDFTOTEXT_BIN explicitly. Error message gets a Windows install hint (scoop install poppler) and setx example.
  • Pre-existing test 'honors BROWSE_BIN when it points at a real executable' was hardcoded /bin/sh — made cross-platform via a REAL_EXE constant (cmd.exe on win32, /bin/sh on POSIX). This was a Windows-CI blocker on its own.

Test plan

  • bun test make-pdf/test/browseClient.test.ts make-pdf/test/pdftotext.test.ts on win32 — 29 pass, 0 fail (12 new assertions: findExecutable POSIX/win32-extension-probe/null; resolveBrowseBin GSTACK_BROWSE_BIN + BROWSE_BIN + precedence + quote-strip; same shape for resolvePdftotext + Windows install hint in error message)
  • POSIX CI — relying on the existing matrix. POSIX branch returns unchanged behavior; Bun.which('browse') on Linux/macOS resolves via PATH the same way execFileSync('which', 'browse') did, minus the Git Bash dependency.

What this PR doesn't do

🤖 Generated with Claude Code


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

…xt on Windows

Extends v1.24.0.0's Bun.which + GSTACK_*_BIN override pattern (introduced in
browse/src/claude-bin.ts via garrytan#1252) to the two other binary resolvers in the
codebase: make-pdf/src/browseClient.ts:resolveBrowseBin and
make-pdf/src/pdftotext.ts:resolvePdftotext.

Same Windows quirks (fs.accessSync(X_OK) degrades to existence-check; `which`
isn't available outside Git Bash; bun --compile --outfile X emits X.exe), same
Bun.which-based fix shape, same env override convention.

Changes:
  - GSTACK_BROWSE_BIN / GSTACK_PDFTOTEXT_BIN as the v1.24-aligned overrides;
    BROWSE_BIN / PDFTOTEXT_BIN remain as back-compat aliases.
  - Bun.which() replaces execFileSync('which', ...) for PATH lookup. Handles
    Windows PATHEXT natively; no more `where`-vs-`which` branch.
  - findExecutable(base) helper exported from each module, probes .exe/.cmd/.bat
    after the bare-path miss on win32. Linux/macOS behavior is bit-identical
    (isExecutable short-circuits before the win32 branch ever runs).
  - macCandidates renamed posixCandidates (always was — /opt/homebrew, /usr/local,
    /usr/bin). No Windows candidates added; Poppler installs scatter across
    Scoop/Chocolatey/portable zips and guessing causes false positives.
  - Error messages get a Windows install hint (scoop install poppler / oschwartz10612)
    and `setx` example for GSTACK_*_BIN.
  - Pre-existing test 'honors BROWSE_BIN when it points at a real executable'
    was hardcoded /bin/sh — made cross-platform via a REAL_EXE constant
    (cmd.exe on win32, /bin/sh on POSIX). Was a Windows-CI blocker on its own.

Coordination: PR garrytan#1094 (@BkashJEE) covered browseClient.ts independently with a
narrower scope; this PR's pdftotext + cross-platform tests + GSTACK_*_BIN naming
are additive. Either order of merge works.

Test plan:
  - bun test make-pdf/test/browseClient.test.ts make-pdf/test/pdftotext.test.ts
    on win32 — 29 pass, 0 fail (12 new assertions: findExecutable POSIX/win32/null,
    resolveBrowseBin GSTACK_BROWSE_BIN + BROWSE_BIN + precedence + quote-strip,
    same shape for resolvePdftotext + Windows install hint in error message).
  - POSIX branch unchanged — fs.accessSync(X_OK) on Linux/macOS short-circuits
    before any win32 logic runs, matching the v1.24 claude-bin.ts pattern.
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