[PR #912] [MERGED] fix(app-router): surface Error.cause in dev-server error output #942

Closed
opened 2026-05-06 13:10:59 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/912
Author: @james-elicx
Created: 4/26/2026
Status: Merged
Merged: 4/26/2026
Merged by: @james-elicx

Base: mainHead: claude/lucid-maxwell-451364


📝 Commits (2)

  • e1cce29 fix(app-router): flatten Error.cause chain into dev-server error output
  • 0ce5f54 fix: address bonk review feedback

📊 Changes

4 files changed (+400 additions, -7 deletions)

View changed files

📝 packages/vinext/src/entries/app-rsc-entry.ts (+19 -1)
packages/vinext/src/utils/error-cause.ts (+107 -0)
📝 tests/__snapshots__/entry-templates.test.ts.snap (+108 -6)
tests/error-cause.test.ts (+166 -0)

📄 Description

Summary

Vite's dev-server "Internal server error:" formatter (buildErrorMessage in vite-plus-core) builds output from err.message and err.stack only, silently dropping err.cause. A wrapper like new Error("Failed query", { cause: pgError }) would surface in the dev console as just Failed query — the actual ECONNREFUSED, role-missing, or workerd socket error in .cause was invisible.

This PR adds flattenErrorCauses(err), called at the App Router RSC handler boundary before any error escapes to Vite. It walks the .cause chain (depth-capped, cycle-safe) and embeds each cause's message into err.message and stack frames into err.stack as at lines (so they survive Vite's cleanStack /^\s*at/ filter).

Before

[vite+] Internal server error: Failed query: select ...
      at PostgresJsPreparedQuery.queryWithCache ...

After

[vite+] Internal server error: Failed query: select ...
  [cause]: ECONNREFUSED 127.0.0.1:5432
      at PostgresJsPreparedQuery.queryWithCache ...
      at [cause: ECONNREFUSED 127.0.0.1:5432]
      at <postgres driver frames>

Notes

  • Marked idempotent via a non-enumerable Symbol.for("vinext.errorCausesFlattened") so Node's util.inspect output (which already shows .cause since Node 16.9) is unaffected — no doubled cause rendering in prod logs.
  • Pages Router catches errors locally and uses console.error(err)util.inspect, which already shows .cause, so no change needed there.
  • Out of scope: production digest sites (__sanitizeErrorForClient, rscOnError, app-ssr-entry) also exclude cause from their hash input — different errors with different causes can collide on the same digest. Happy to do that as a follow-up if wanted.

Test plan

  • Unit tests for flattenErrorCauses in tests/error-cause.test.ts — 10 tests including a direct repro that mirrors Vite's formatter and asserts ECONNREFUSED is dropped before flattening and surfaced after; plus multi-level chains, idempotency, cyclic graphs, non-Error causes, null/undefined/string inputs, and marker non-enumerability
  • Snapshot updates in tests/__snapshots__/entry-templates.test.ts.snap (6 snapshots) reflect the try/catch around _handleRequest
  • vp check passes on all changed files

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/912 **Author:** [@james-elicx](https://github.com/james-elicx) **Created:** 4/26/2026 **Status:** ✅ Merged **Merged:** 4/26/2026 **Merged by:** [@james-elicx](https://github.com/james-elicx) **Base:** `main` ← **Head:** `claude/lucid-maxwell-451364` --- ### 📝 Commits (2) - [`e1cce29`](https://github.com/cloudflare/vinext/commit/e1cce2915c3eef08a6fce2c5daff20cf056b4e20) fix(app-router): flatten Error.cause chain into dev-server error output - [`0ce5f54`](https://github.com/cloudflare/vinext/commit/0ce5f549007012057086a0aad0fc4309007d9cf4) fix: address bonk review feedback ### 📊 Changes **4 files changed** (+400 additions, -7 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/entries/app-rsc-entry.ts` (+19 -1) ➕ `packages/vinext/src/utils/error-cause.ts` (+107 -0) 📝 `tests/__snapshots__/entry-templates.test.ts.snap` (+108 -6) ➕ `tests/error-cause.test.ts` (+166 -0) </details> ### 📄 Description ## Summary Vite's dev-server "Internal server error:" formatter (`buildErrorMessage` in `vite-plus-core`) builds output from `err.message` and `err.stack` only, silently dropping `err.cause`. A wrapper like `new Error("Failed query", { cause: pgError })` would surface in the dev console as just `Failed query` — the actual `ECONNREFUSED`, role-missing, or workerd socket error in `.cause` was invisible. This PR adds `flattenErrorCauses(err)`, called at the App Router RSC handler boundary before any error escapes to Vite. It walks the `.cause` chain (depth-capped, cycle-safe) and embeds each cause's message into `err.message` and stack frames into `err.stack` as `at` lines (so they survive Vite's `cleanStack` `/^\s*at/` filter). ### Before ``` [vite+] Internal server error: Failed query: select ... at PostgresJsPreparedQuery.queryWithCache ... ``` ### After ``` [vite+] Internal server error: Failed query: select ... [cause]: ECONNREFUSED 127.0.0.1:5432 at PostgresJsPreparedQuery.queryWithCache ... at [cause: ECONNREFUSED 127.0.0.1:5432] at <postgres driver frames> ``` ### Notes - Marked idempotent via a non-enumerable `Symbol.for("vinext.errorCausesFlattened")` so Node's `util.inspect` output (which already shows `.cause` since Node 16.9) is unaffected — no doubled cause rendering in prod logs. - Pages Router catches errors locally and uses `console.error(err)` → `util.inspect`, which already shows `.cause`, so no change needed there. - Out of scope: production digest sites (`__sanitizeErrorForClient`, `rscOnError`, `app-ssr-entry`) also exclude cause from their hash input — different errors with different causes can collide on the same digest. Happy to do that as a follow-up if wanted. ## Test plan - [x] Unit tests for `flattenErrorCauses` in `tests/error-cause.test.ts` — 10 tests including a direct repro that mirrors Vite's formatter and asserts `ECONNREFUSED` is dropped before flattening and surfaced after; plus multi-level chains, idempotency, cyclic graphs, non-Error causes, `null`/`undefined`/string inputs, and marker non-enumerability - [x] Snapshot updates in `tests/__snapshots__/entry-templates.test.ts.snap` (6 snapshots) reflect the try/catch around `_handleRequest` - [x] `vp check` passes on all changed files 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 13:10:59 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#942
No description provided.