Skip to content

fix(make-pdf): three bugs that break browse resolution and temp-file writes - #2505

Open
lvthewah wants to merge 3 commits into
garrytan:mainfrom
lvthewah:fix/make-pdf-browse-resolution
Open

fix(make-pdf): three bugs that break browse resolution and temp-file writes#2505
lvthewah wants to merge 3 commits into
garrytan:mainfrom
lvthewah:fix/make-pdf-browse-resolution

Conversation

@lvthewah

Copy link
Copy Markdown

Summary

make-pdf is unusable on a stock global install: $P setup fails at step 1 or 2 and $P generate fails at the write stage. Three independent bugs, each with a one-line-ish fix. All three still reproduce on main at v1.61.0.0.

Found while using make-pdf for real work (a multi-page letter), per the CONTRIBUTING "fix gstack while doing your real work" flow.

The bugs

1. isExecutable() resolves a directory as the browse binary

resolveBrowseBin() tries path.resolve(selfDir, "../browse") as a sibling candidate. ./setup creates a skill directory at ~/.claude/skills/browse (holding a SKILL.md symlink). isExecutable() only called fs.accessSync(p, X_OK), which succeeds on directories because they carry the traverse bit:

path:                /Users/me/.claude/skills/browse
exists:              true
accessSync(X_OK)  -> true    <- old isExecutable() said this IS the binary
statSync().isFile -> false   <- new guard correctly rejects it

So make-pdf "found" a directory and every later browse call died:

[1/5] Checking browse binary... OK (/Users/me/.claude/skills/browse)
[2/5] Launching Chromium... FAIL

Fix: guard with statSync(p).isFile().

2. Temp files land outside browse's safe-dirs allowlist

browseClient.ts already documents this exactly, and already introduced PAYLOAD_TMP_DIR to solve it:

v1.6.0.0 tightened --from-file validation to close a CLI/API parity gap (PR #1103), so os.tmpdir() on macOS (/var/folders/...) now fails validateReadPath.

But only writePayloadFile() ever used the constant. orchestrator.ts (default output, preview HTML, tmpFile()) and setup.ts (smoke fixture + output) still used os.tmpdir(), so browse rejects them:

[4/5] Generating smoke-test PDF...
      FAILED: browse pdf exited 1: Path must be within: /private/tmp, ...

Fix: export PAYLOAD_TMP_DIR, use it at those five sites. Side benefit: the default output path now matches what SKILL.md already documents ($P generate letter.md/tmp/letter.pdf).

3. Chromium smoke test uses a blocked URL scheme

browse permits only http:, https:, file:. setup.ts opened about:blank:

[2/5] Launching Chromium... FAIL
Chromium failed to launch: browse newtab exited 1:
  Blocked: scheme "about:" is not allowed.

Fix: write a one-line blank HTML file under PAYLOAD_TMP_DIR and open that. Network-free, cleaned up in the existing finally.

Verification

bun test make-pdf/test/185 pass, 0 fail.

Built the branch and ran the flow that was broken:

[1/5] Checking browse binary... OK (.../gstack/browse/dist/browse)
[2/5] Launching Chromium... OK (tab 2)
[3/5] Checking pdftotext (optional)... OK
[4/5] Generating smoke-test PDF...
      PASSED.
[5/5] All checks passed.

Also rendered a real 2,212-word / 8-page document end to end.

Notes

  • Three commits, one per bug, so they can be taken or dropped independently.
  • No VERSION bump or CHANGELOG entry: those are branch-scoped to your ship flow and would conflict across PRs. Happy to add one if you'd rather.
  • Not fixed here, flagged only: diagram-prepass.ts:294 now declares its own duplicate PAYLOAD_TMP_DIR. Worth collapsing onto the exported one, but that felt like separate scope.
  • Found on macOS 27.0 (arm64), Bun 1.3.14. Bugs 1 and 3 are platform-independent; bug 2 is macOS-specific, since os.tmpdir() only diverges from /tmp there.

lvthewah and others added 3 commits August 10, 2026 11:43
isExecutable() used only fs.accessSync(p, X_OK), which succeeds for
directories because they carry the execute (traverse) bit.

./setup creates a skill directory at ~/.claude/skills/browse containing a
SKILL.md symlink. resolveBrowseBin()'s third sibling candidate resolves to
exactly that path, so it was returned as the browse "binary". Every
subsequent browse invocation then failed with EACCES, surfacing as:

  [1/5] Checking browse binary... OK (/Users/me/.claude/skills/browse)
  [2/5] Launching Chromium... FAIL

Guard with statSync(p).isFile() so only real files match.

Co-Authored-By: claude-flow <ruv@ruv.net>
…list

browseClient already documented this: v1.6.0.0 tightened --from-file
validation (PR garrytan#1103), so os.tmpdir() on macOS (/var/folders/...) fails
browse's validateReadPath. The PAYLOAD_TMP_DIR constant was introduced to
fix it, but only writePayloadFile() used it.

orchestrator.ts (default output, preview HTML, tmpFile) and setup.ts
(smoke fixture and output) still used os.tmpdir(), so every one of those
paths is rejected by browse:

  [4/5] Generating smoke-test PDF...
        FAILED: browse pdf exited 1: Path must be within: /private/tmp, ...

Export PAYLOAD_TMP_DIR and use it at those five sites. This also makes the
default output path match the documented behaviour in SKILL.md, which
already says `$P generate letter.md` writes /tmp/letter.pdf.

Drops the now-unused os imports from both files.

Co-Authored-By: claude-flow <ruv@ruv.net>
browse's URL validation permits only http:, https: and file: schemes, so
the setup smoke test aborted before it ever reached the PDF stage:

  [2/5] Launching Chromium... FAIL
  Chromium failed to launch: browse newtab exited 1:
    Blocked: scheme "about:" is not allowed.

Write a one-line blank HTML file under PAYLOAD_TMP_DIR (inside browse's
safe-dirs allowlist) and open that instead. Keeps the check network-free,
and removes the file in the existing finally block.

Co-Authored-By: claude-flow <ruv@ruv.net>
@trunk-io

trunk-io Bot commented Aug 10, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

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