Skip to content

fix: resolve three macOS startup issues (posix_spawnp, nvm PATH, session restore overlay)#67

Open
buallen wants to merge 1 commit intovultuk:mainfrom
buallen:fix/macos-pty-spawn-and-session-restore
Open

fix: resolve three macOS startup issues (posix_spawnp, nvm PATH, session restore overlay)#67
buallen wants to merge 1 commit intovultuk:mainfrom
buallen:fix/macos-pty-spawn-and-session-restore

Conversation

@buallen
Copy link
Copy Markdown

@buallen buallen commented Mar 23, 2026

Summary

This PR fixes three issues discovered while running claude-code-web as a persistent macOS launchd service accessed via ngrok. All three prevent the interface from working on a fresh macOS install with nvm.

Bug 1 — spawn-helper missing execute permission → posix_spawnp failed

Root cause: npm does not guarantee that executable bits are preserved when installing from the registry. node-pty's spawn-helper binary lands as -rw-r--r-- instead of -rwxr-xr-x, causing every single pty.spawn() call to throw posix_spawnp failed.

Fix: Added scripts/postinstall.js (invoked via "postinstall" in package.json) that walks all node_modules/node-pty/prebuilds/*/spawn-helper files and restores the execute bit if missing.

Bug 2 — nvm-installed claude not found when running as a service

Root cause: When cc-web runs under launchd (macOS) or systemd (Linux), PATH does not include nvm's per-version bin/ directory. findClaudeCommand() fell back to the short name "claude", which which found at runtime but node-pty's native posix_spawnp could not resolve later.

Fix: findClaudeCommand() now dynamically reads ~/.nvm/versions/node/ and prepends all discovered bin/claude absolute paths to the search list, ensuring an absolute path is always returned.

Bug 3 — posix_spawnp cannot exec a bare JS script (nvm symlink)

Root cause: The claude binary installed via nvm is a symlink → cli.js. node-pty passes the path directly to posix_spawnp, which on some macOS configurations fails to exec a script file even when the shebang is valid.

Fix: startSession() resolves symlinks before spawning. When the resolved target ends in .js, it spawns process.execPath <cli.js> [args] instead of the script directly.

Bug 4 — "Start Claude" overlay hidden on page reload for inactive sessions

Root cause: init() called this.hideOverlay() unconditionally after await switchToTab(). Because switchToTab awaits joinSession (which resolves when session_joined is received), the overlay state set by the session_joined handler (showOverlay('startPrompt') for inactive sessions) was immediately overridden by the subsequent hideOverlay() microtask continuation — leaving users with a blank terminal and no way to start Claude.

Fix: Removed the unconditional hideOverlay() from init(). The session_joined handler is already authoritative over overlay state and handles both the active (hide) and inactive (show startPrompt) cases correctly.

Test plan

  • Fresh npm install -g claude-code-web on macOS — cc-web starts without posix_spawnp failed
  • Running as launchd service — claude is found and starts correctly
  • nvm-managed node install — claude spawns via node cli.js
  • Close browser tab / refresh page with an inactive session — "Start Claude" button is visible
  • Refresh page with an active Claude session — terminal resumes correctly without overlay

🤖 Generated with Claude Code

1. spawn-helper missing execute permission (posix_spawnp failed)
   node-pty's spawn-helper binary loses its execute bit after npm
   install on macOS. Added a postinstall script that detects and
   repairs the permission, fixing the "posix_spawnp failed" error
   that affects all macOS users on a fresh install.

2. nvm-installed claude not found under launchd/systemd
   When cc-web runs as a background service (launchd on macOS,
   systemd on Linux) PATH does not include nvm's bin directory.
   findClaudeCommand() now dynamically discovers all nvm node
   versions and probes their bin dirs before falling back to
   short-name lookup, so the absolute path is always returned and
   node-pty can spawn it correctly.

3. node-pty cannot posix_spawnp a JS script (nvm installs)
   The claude binary installed via nvm is a symlink to cli.js.
   node-pty's posix_spawnp cannot exec a bare JS file. startSession()
   now resolves symlinks and, when the target is a .js file, spawns
   `process.execPath cli.js [args]` instead of the script directly.

4. Restored sessions hide "Start Claude" overlay on page reload
   init() was calling hideOverlay() unconditionally after
   switchToTab(), racing with and overriding the startPrompt overlay
   that session_joined correctly shows for inactive sessions. Removed
   the redundant hideOverlay() call so the session_joined handler
   remains authoritative over overlay state.

Co-Authored-By: Claude Sonnet 4.6 <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