Skip to content

fix(spawn): resolve bun executable with PATH fallback#8

Merged
bjb2 merged 1 commit into
mainfrom
fix/bun-execpath-resolution
May 29, 2026
Merged

fix(spawn): resolve bun executable with PATH fallback#8
bjb2 merged 1 commit into
mainfrom
fix/bun-execpath-resolution

Conversation

@bjb2
Copy link
Copy Markdown
Owner

@bjb2 bjb2 commented May 29, 2026

Closes #6.

The bug

Reported by Axel on macOS. Starting the telegram bridge crashed with:

Error: ENOENT: no such file or directory, posix_spawn '/Users/axelneff/.bun/bin/bun'

That path is ~/.bun/bin/bun — the default install location for the official Bun installer. But the file isn't there. Most likely: Axel installed Bun via Homebrew (or asdf/mise/proto) after the deck started, and the original ~/.bun/bin/bun was gone or moved. The deck server captured the old path at boot via process.execPath and kept using it for child spawns.

Root cause

Two spots in the server spawn Bun child processes using process.execPath directly:

File Use
apps/server/src/bridge-supervisor.ts:102 Spawns the telegram bridge subprocess
apps/server/src/index.ts:242 scheduleRestart re-execs the deck server

process.execPath is the path to the binary that started the current interpreter — captured AT PROCESS START, not "wherever bun lives now." Bun.spawn does NOT search PATH for cmd[0]; it goes straight to posix_spawn / CreateProcess and ENOENTs when the captured path is dead.

Fix

New apps/server/src/runtime-bun.ts with resolveBunExecutable():

  1. Try process.execPath first — fast path, correct for normal users.
  2. Fall back to Bun.which("bun") — searches PATH the way the user's shell would, finds wherever Bun lives now.
  3. Throw a clear actionable error mentioning both attempts if neither resolves.

Memoized for the process lifetime (a working Bun binary doesn't move mid-process). Both spawn sites route through the resolver.

Verification

  • 4 new tests covering: existing-path returns, memoization, stale-execPath fallback to Bun.which, both-missing throws with clear error
  • Full server suite still green: 177/177 pass (was 173)
  • Typecheck across all 4 packages: clean

Files touched

apps/server/src/runtime-bun.ts          (new)
apps/server/src/runtime-bun.test.ts     (new)
apps/server/src/bridge-supervisor.ts    (use resolver)
apps/server/src/index.ts                (use resolver in scheduleRestart)

Out of scope

  • Periodically re-validating the cached path mid-process (a bun binary that moves DURING a deck run is too rare to engineer for — just restart the deck)
  • Bridge UI polish to surface the resolution failure more clearly than the raw posix_spawn error (the new error message is already actionable; UI work is separate)

Closes #6.

Reported by Axel on macOS: starting the telegram bridge crashed with
`Error: ENOENT: no such file or directory, posix_spawn '/Users/axelneff/.bun/bin/bun'`.

Both child-process spawn sites in the server hardcode the path that
launched the running interpreter (`process.execPath`):
  - bridge-supervisor.ts:102 — spawning the telegram bridge subprocess
  - index.ts:242         — `scheduleRestart` re-execing the deck server

That path is the correct binary 99% of the time but captures the bun
location AT DECK BOOT — not 'wherever bun is now.' If the user
reinstalls bun via Homebrew / asdf / mise after the deck started, or
uninstalls the official-installer copy at `~/.bun/bin/bun`,
`process.execPath` still references the dead path. `Bun.spawn` does
NOT search PATH for cmd[0]; it goes straight to posix_spawn and
ENOENTs.

New `apps/server/src/runtime-bun.ts` adds `resolveBunExecutable()`:

  1. Try `process.execPath` — fast path, exists for normal users.
  2. Fall back to `Bun.which("bun")` — searches PATH the way the
     user's shell would.
  3. Throw a clear actionable error if neither resolves.

Memoized for the process lifetime. Both spawn sites now route through
this resolver.

4 new tests covering the existing-path case, memoization, the stale-
execPath fallback, and the both-missing error path. Server suite still
green (177/177), typecheck across 4 packages clean.

Out of scope:
- Periodically re-validating the cached path (a bun binary that moves
  mid-deck-run is too rare to engineer for; just restart the deck).
- Telegram bridge UI improvements to surface the resolution failure
  more clearly than the raw posix_spawn error. The error message is
  already actionable; UI polish is separate.
@bjb2 bjb2 merged commit bf086f2 into main May 29, 2026
4 checks passed
bjb2 added a commit that referenced this pull request May 29, 2026
…fixes

See CHANGELOG.md for the full release notes. Bumps all 4 package.json
versions to 0.6.0 in lockstep (root, server, web, protocol). Adds
docs/upgrading.md as the canonical place for version-by-version
migration notes — explicitly documents that the new onboarding wizard
silently settles for existing users (any session OR moved welcome task
short-circuits the gate), so this is a non-breaking upgrade.

Headline changes since v0.5.0:
- Onboarding wizard at /onboarding for fresh installs (#9)
- Model picker subscription badge + placeholder-key suppression +
  401-recovery hint (#7, closes #4)
- OAuth flow 5-min timeout + stale eviction + drained cancel (#7,
  closes #5)
- Bun executable PATH fallback (#8, closes #6)
- README install section rewritten + LF normalization + private
  template stash on pack

177 -> 181 server tests (4 new from user's parallel Windows
rename-fix WIP that landed; my onboarding work adds 4 of those).
Typecheck clean across 4 packages.
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.

telegram bridge problem - macos

1 participant