Skip to content

Commit 9945827

Browse files
ducmngxclaude
andcommitted
fix(dev): downgrade to Node 22 LTS to restore hot reload
Root cause: Node 25.0.0 removed the callback-parameter form of Worker#terminate() (nodejs/node#58528), which broke Next's RSC worker pool with thousands of "Unexpected response from worker: undefined" errors that blocked all client-side fetches in dev mode. Changes: - .nvmrc: pin to Node 22 - package.json: add engines: { "node": "22.x" } - server.ts: remove webpack: dev hedge (Turbopack works fine on Node 22) - CLAUDE.md + docs/progress.md: update Node version notes npm run dev now starts cleanly with Turbopack and hot reload on Node 22.22.2. Production builds work on any Node >= 20. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 79ff16f commit 9945827

5 files changed

Lines changed: 12 additions & 8 deletions

File tree

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22

CLAUDE.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ Phases 1–3 are complete; Phase 4 has landed cost tracking and partial telemetr
2121

2222
```bash
2323
npm run dev # node --watch --watch-path=server.ts --require tsx/cjs server.ts
24-
# Custom server + Next + WebSocket on :3000
25-
# Uses Node's native --watch (not tsx --watch) for Node.js v25 compatibility
24+
# Custom server + Next (Turbopack) + WebSocket on :3000 with hot reload
25+
# Requires Node 22 LTS — see .nvmrc. Node 25 breaks RSC workers (nodejs/node#58528)
2626
npm run start # tsx server.ts — same server, no watch
2727
npm run build # Production build (next build)
2828
npm run db:generate # Regenerate Drizzle migrations after schema changes
@@ -43,7 +43,7 @@ node scripts/seed.mjs
4343

4444
**Never run `next dev` directly** — it skips the custom server and the dashboard will lose live updates.
4545

46-
**Node.js v25 note:** `tsx --watch` crashes on Node.js v25 due to a named-pipe IPC incompatibility. The dev script uses `node --watch --require tsx/cjs` instead. `--watch-path=server.ts` limits watching to `server.ts` only so Next.js's own file changes don't trigger server restarts.
46+
**Node.js version:** Dev requires **Node 22 LTS** (`.nvmrc` + `engines: { "node": "22.x" }` in `package.json`). Node 25 breaks Next's RSC worker pool (nodejs/node#58528 removed `Worker#terminate()` callback form). The dev script uses `node --watch --require tsx/cjs` instead of `tsx --watch`; `--watch-path=server.ts` limits watching to `server.ts` only so Next.js's own file changes don't trigger server restarts.
4747

4848
## Architecture
4949

@@ -178,12 +178,12 @@ Deduplication: commits matched by hash are skipped so re-scanning is safe.
178178

179179
**Phase 3 (complete):** custom HTTP server hosting Next + a `ws` server on `/ws`, React `WebSocketProvider` with channel subscriptions, broadcast helpers wired into every mutating API route, live-updating session list and session detail pages, `LiveIndicator`, unified `/api/ingest/hook` endpoint, `subspan-hook` bin + `subspan hooks install/uninstall/status` CLI commands for Claude Code integration, tool call event type + `ToolCallCard` and "Tool Calls" tab in session detail, `sessions.model` and `sessions.external_session_id` columns.
180180

181-
**Dev server (Node 25):** broken both ways. Turbopack crashes on `Persisting failed: Unable to write SST file`; the webpack fallback (wired via `next({ dev, webpack: dev })` in `server.ts`) renders HTML but spams `TypeError: Unexpected response from worker: undefined` and blocks client-side fetches. Canonical workflow on Node 25 is `npm run build && npm start`. The `webpack: dev` flag is retained as a hedge (harmless in prod — the option is ignored when `dev === false`).
181+
**Dev server:** requires Node 22 LTS. `npm run dev` starts Turbopack with hot reload. Node 25 is unsupported for dev`Worker#terminate()` API change (nodejs/node#58528) breaks Next's RSC worker pool. Production builds (`npm run build && npm start`) work on any Node ≥ 20.
182182

183183
**Phase 4 (in progress):**
184184
- ✅ Cost tracking per session — `src/lib/pricing.ts`, `CostBadge`, `TokenMetrics`, token/cost metric keys.
185185
- ✅ Subagent tracking — `Task` tool detection in hook ingest, `SubagentCard` with time-range nesting, `subagent_count`/`subagent_failed_count` metrics.
186186
- ✅ KPI Dashboard — `/dashboard` replaced with 4 KPI cards + live strip + charts (`AgentUsagePie`, `CostOverTimeChart`) + top subagents + agent cost breakdown. Old sessions table moved to `/sessions`. Backed by `/api/dashboard/stats` with 30s in-memory cache.
187187
- ✅ Related sessions sidebar on session detail — same `(projectId, branch)` overlapping time window.
188188
- ⏳ Team management, cloud deployment still to come.
189-
- **Node 25 dev still broken**Turbopack hits an SST cache crash; the webpack fallback (`next({ dev, webpack: dev })` in `server.ts`) hits an RSC worker bug that blocks client fetches. Workaround remains `npm run build && npm start`. See `docs/hot-reload-options.md`.
189+
- **Node 22 LTS is the supported dev runtime**hot reload works via Turbopack. Node 25 remains unsupported for dev (RSC worker bug, nodejs/node#58528). See `docs/progress.md`.

docs/progress.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ Last updated: 2026-04-13
6262
- [x] **Verify subagent nesting visually** — done 2026-04-13. Architecture review session shows 3 violet SubagentCards with nested tool calls + compact context bars (orange ~75%). Refactor session shows Plan subagent with 5 nested calls. Context-snapshot events hidden from timeline (raw JSON display bug fixed). Task-scoped snapshots re-seeded to fall within Task time windows.
6363

6464
### Medium-term
65-
- [ ] **Node 25 dev mode — still broken.** Partial progress 2026-04-12 at 7d2425b: pinned dev to webpack via `next({ dev, webpack: dev })` to dodge the Turbopack SST cache crash. Pages render HTML (200 OK) but Node 25 + webpack RSC workers produce thousands of `TypeError: Unexpected response from worker: undefined` errors that block client-side fetches — dashboards stay stuck on "Loading...". Keeping the webpack flag as a one-line hedge (harmless in prod) but the canonical workflow on Node 25 is `npm run build && npm start`. Revisit when either Turbopack or the webpack RSC worker path stabilizes on Node 25+.
66-
- [ ] **Hot reload replacement**still open. Webpack flag alone doesn't get us a working dev loop because of the RSC worker issue above. Next try: investigate whether `node --watch` + `--require tsx/cjs` is the confounder (try plain `tsx server.ts` without node's native watcher), or whether tsx itself is incompatible with webpack's RSC worker on Node 25.
65+
- [x] **Node 25 dev mode — resolved via Node 22 LTS downgrade (2026-04-13).** Root cause: Node 25.0.0 removed the callback-parameter form of `Worker#terminate()` (SEMVER-MAJOR, nodejs/node#58528); Next's RSC worker pool relied on it, causing thousands of `TypeError: Unexpected response from worker: undefined` errors that blocked all client-side fetches. Turbopack SST crash (vercel/next.js#90691) is a separate upstream bug. Fix: pinned project to Node 22.22.2 LTS via `.nvmrc` + `engines: { "node": "22.x" }` in `package.json`, removed `webpack: dev` hedge from `server.ts`, rebuilt `better-sqlite3` for Node 22 ABI. Dev server now starts cleanly with Turbopack and hot reload. Canonical dev command remains `npm run dev` (Node 22). Revisit Node 25 when Next ships a worker_threads compatibility fix.
66+
- [x] **Hot reload**working on Node 22 LTS. `npm run dev` starts Turbopack dev server with hot reload via `node --watch --require tsx/cjs server.ts`.
6767
- [x] **Migrate `middleware.ts``proxy.ts`** — done 2026-04-12 at 693184c. Pure rename, no API changes.
6868
- [x] **`package.json` cleanup** — done 2026-04-12 at 28de0e5. `graphify` removed from dependencies.
6969
- [x] **Event data double-encoding cleanup** — done 2026-04-13 at b24c29f. Removed manual JSON.stringify from hook route; Drizzle mode:'json' handles serialization. Read paths remain tolerant of old rows.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
"name": "subspan",
33
"version": "0.1.0",
44
"private": true,
5+
"engines": {
6+
"node": "22.x"
7+
},
58
"scripts": {
69
"dev": "node --watch --watch-path=server.ts --require tsx/cjs server.ts",
710
"build": "next build",

server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { seedDefaultsIfNeeded } from './src/lib/db/seed-defaults';
88
const dev = process.env.NODE_ENV !== 'production';
99
const port = parseInt(process.env.PORT ?? '3000', 10);
1010

11-
const app = next({ dev, webpack: dev });
11+
const app = next({ dev });
1212
const handle = app.getRequestHandler();
1313

1414
app.prepare().then(() => {

0 commit comments

Comments
 (0)