Skip to content

fix: keep the Bun dev server correct and flat across hot reloads - #1584

Merged
vivek7405 merged 1 commit into
mainfrom
fix/bun-dev-hot-reload
Oct 7, 2026
Merged

vivek7405 merged 1 commit into
mainfrom
fix/bun-dev-hot-reload

Conversation

@vivek7405

Copy link
Copy Markdown
Collaborator

Closes #1575

Found by a stress script that edits a running webjs dev app the way an AI agent does (bursts, partial writes, syntax errors then fixes, renames, deletes, atomic writes, a whole feature at once, checkout-like churn), against a fresh bun create webjs --db postgres app and Crisp's app.

Root causes (Bun, bun --hot)

  • Every edit re-ran the CLI, so startServer ran again: a second handler, watchers, SSE hub, process handlers, instrumentation.register() and Bun.plugins, none disposed. ~25 MB per edit (153 MB to 5.8 GB over 225 edits), after which bun --hot stopped reloading and component edits were never served.
  • A re-run built its route table before its watchers existed, so files changed while it booted were invisible to the instance that served (404s, unknown actions).
  • Bun keeps a replaced file's old source forever (one atomic save and the page served its old version until restart), keeps a stale directory listing for a # import of a new file next to *.server.* modules, and hangs on re-importing a module that failed to parse.
  • A *.server.* edit restarted the process (connection refused for 0.3 to 2 s); every dev re-import used a random query, leaking a module instance per request (~60 KB).
  • Analysis caches restarted cold; the graph cache kept stale resolutions.

Fix

  • dev/hot-host.js: the first run owns the process; a re-run (CLI fast path lib/dev-hot-rerun.js) only resets the registry and re-derives the analysis. Framework upgrade mid-session exits the child for the supervisor to restart.
  • App modules load through a fresh-reading plugin (dev/bun-app-source.js), # aliases resolve through the app map (dev/bun-alias-resolve.js); the watcher pokes a temp-dir sentinel for a registry reset when needed, instead of restarting. The supervisor narrows restarts to instrumentation.* / env.* once the child announces in-place reload over IPC.
  • devImport: content-keyed specifiers, never re-importing a failed one.
  • Per-file caches for elision and the orphan scan; graph caches specifiers, not resolutions; watcher debounce 80 to 50 ms; routes.d.ts temp-name race fixed.

Results (stress script, 43 checks)

  • Scaffold before: 4 fail, 13 slow, 5.8 GB after 225 edits. After: 43/43, p50 17 ms, max ~105 ms; RSS flat over 400 edits.
  • Crisp before: 4 fail, 20 slow, p50 585 ms, max 2.2 s. After: 0 fail, p50 34 ms, max ~270 ms.
  • Counterfactual: bun test/bun/dev-reload-stress.mjs against main's server/cli: 4 fail.

Tests

  • npm test green (4973 pass); Bun matrix: all test/bun/dev-*.mjs green under Bun; new test/bun/dev-reload-stress.mjs (both runtimes), packages/server/test/dev/hot-reload-robustness.test.js, supervisor tests.
  • Browser / e2e: N/A, no browser-side change (the reload frame protocol is unchanged).
  • Docs: runtime reference, AGENTS.md, cli/server AGENTS, docs site runtime/ssr/websockets/configuration.

https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo

@vivek7405 vivek7405 self-assigned this Oct 7, 2026
@pilots-run

pilots-run Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Preview for 771afcc was not built: compose file has unsupported keys

Next: fix the listed keys in the compose file

Under bun --hot every edit re-ran the whole dev server without disposing
the previous one: memory grew ~25 MB an edit until bun --hot stopped
reloading, instrumentation ran again per edit, and a new instance built
its route table before its watchers existed, so files changed while it
booted were never seen (404s for routes that exist, unknown actions).
Bun also kept the old source of a replaced file forever, a stale
directory listing for new files next to server modules, and hung on
re-importing a module that failed to parse; a server-module edit
restarted the process, dropping requests for up to 2 s.

The first run now owns the process and a re-run only resets the module
registry; app modules load through a fresh-reading plugin, # aliases
resolve through the app's map, the watcher pokes a sentinel instead of
restarting, dev re-imports are keyed by content (no per-request module
leak), and the analysis caches survive rebuilds. A stress script that
edits like an AI agent runs on both runtimes.

Closes #1575

Claude-Session: https://claude.ai/code/session_01SZ72LSPAo4NvYvBDSD6RLo
@vivek7405
vivek7405 force-pushed the fix/bun-dev-hot-reload branch from 22e2eee to 771afcc Compare October 7, 2026 12:19
@vivek7405
vivek7405 merged commit a6eb163 into main Oct 7, 2026
1 check failed
@vivek7405
vivek7405 deleted the fix/bun-dev-hot-reload branch October 7, 2026 12:19
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.

fix: keep the Bun dev server correct and flat across hot reloads

1 participant