Skip to content

Windows Pulse port (#1733): pulse.ts exits silently before ever binding the port #2223

Description

@aveneri

Summary

With the parsing bug from the BOM issue fixed, pulse.ts starts on Windows and exits immediately with code 0 and zero log output — no error, no crash message, nothing. It never reaches Bun.serve(), so Pulse never actually comes up.

Environment: Windows 10 Pro, PowerShell 7 and 5.1 both reproduce it, Bun 1.3.10.

Root cause

The singleton guard at the top of main() in pulse.ts does:

const oldPid = parseInt((await Bun.file(PID_PATH).text()).trim(), 10)

On this Windows/Bun combination, when PID_PATH doesn't exist yet — true on every first boot, since this is exactly the file this check itself creates — the awaited promise never settles. It doesn't throw, doesn't reject, it just never resolves. With nothing else keeping the event loop alive at that point in main(), the process quietly exits(0) with the promise still pending, before the surrounding try/catch or the outer main().catch() ever run. No log line, no stack trace.

Confirmed with targeted instrumentation: execution reaches the line, logs the path about to be read, and the process exit event fires with no further output. A standalone minimal repro of the exact same Bun.file(missingPath).text() call, with the same imports, does NOT reproduce the hang on its own — so this looks specific to some interaction with the rest of the file's module graph rather than something reproducible as a Bun.file bug in isolation. Flagging as observed-and-worked-around rather than fully root-caused.

Repro

  1. Fresh Windows box, no prior Pulse state (PULSE/state/pulse.pid does not exist).
  2. bun run pulse.ts.
  3. Process exits ~instantly, exit code 0, no console output at all.

Fix

Guard the read with an existence check first, since the file legitimately may not exist on first boot — this sidesteps the hang regardless of its exact cause, and is a reasonable belt-and-suspenders check on any platform:

-    const oldPid = parseInt((await Bun.file(PID_PATH).text()).trim(), 10)
+    const oldPid = existsSync(PID_PATH) ? parseInt((await Bun.file(PID_PATH).text()).trim(), 10) : NaN

(existsSync is already imported at the top of pulse.ts.)

Separately noticed, not fixed here

The same guard's duplicate-instance detection shells out to ps -p <pid> -o command=, which doesn't exist on Windows. It fails silently into the existing catch block, so it isn't blocking, just a soft gap: the "another pulse.ts is already running" warning never fires on Windows. Needs a real Windows equivalent (e.g. Get-CimInstance Win32_Process) rather than a one-line patch, so leaving it as a known gap rather than folding a fix in here.

Testing performed

With this fix plus the BOM fix and the status-timeout fix (filed separately) applied together, verified the full lifecycle on the same Windows machine:

install → start → status (yes, healthz 200) → stop → status (no) → uninstall (task removed) → install → start → status (yes)

Pulse now runs continuously as a current-user scheduled task with no admin rights required, matching the design in #1733.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions