devtools-vite console pipe: infinite feedback loop with Vite 8 `server.forwardConsole` — one console entry floods browser + terminal
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 3/5
- Geschätzter Aufwand
- 1-2 Tage
- Anfängerfreundlichkeit
- 72/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Ruhig
- Tech-Stack
- typescript, vite
- Bereich
- build-system, devtools
Rechercherichtung
Beginne mit der Reproduktion in den vier Dateien: vite.config.js, entry-server.js, main.js und package.json, und verfolge anschließend die serverseitige console-pipe-Injektion, die vom SSR-Einstiegspunkt verwendet wird. Reproduziere das Verhalten mit Vite 8 und beobachte sowohl die Browser- als auch die Terminalausgabe. Als abgeschlossen gilt die Aufgabe, wenn ein gesetzter Console-Fehler begrenzt bleibt, von Vite weitergeleitete Client-Logs nicht erneut übertragen werden und echte Server-Logs weiterhin genau einmal gespiegelt werden.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Which project does this relate to?
Devtools
Describe the bug
@tanstack/devtools-vite's console pipe enters an infinite feedback loop with Vite 8's server.forwardConsole (on by default): a single console.error/console.warn anywhere — client or server — floods both the browser console and the terminal with thousands of nested lines per minute, indefinitely:
9:27:59 PM [vite] (client) [console.error] [Server] 9:27:59 PM [vite] (client) [console.error] [Server] 9:27:59 PM [vite] (client) [console.error] [Se…
The cycle:
- Any console entry in the browser → Vite 8
forwardConsolere-prints it in the terminal via the server logger (… [vite] (client) [console.error] …). - The console-pipe's server-side console patch intercepts that print. Its recursion guard only skips messages containing
[TSD Console Pipe]/[@tanstack/devtools, so a Vite-forwarded client log is treated as a server log and broadcast to the browser over SSE. - The browser mirror prints it (
%c[Server]%c …) → Vite forwards it again → goto 1. Each hop nests the previous message inside itself.
In a real TanStack Start app (Vite 8.0.16, devtools-vite 0.8.1) we measured one failed fetch turning into 37,779 browser console entries in 30s and a 2.5 GB terminal log in about a minute.
The related fix in 0.6.1 ("console pipe feedback loop … with Vite 8") does not cover this path — 0.7.0 and 0.8.1 both reproduce.
Your Example Website or App
See minimal reproduction below (4 files)
Steps to Reproduce the Bug or Issue
package.json
{
"name": "tsd-console-loop-repro",
"private": true,
"type": "module",
"scripts": { "dev": "vite" },
"devDependencies": {
"@tanstack/devtools-vite": "0.8.1",
"vite": "8.0.16"
}
}
vite.config.js — bare Vite + devtools + a 10-line SSR middleware (the pipe's server console patch is only injected into SSR modules, so a plain SPA doesn't show it):
import { devtools } from '@tanstack/devtools-vite'
export default {
appType: 'custom',
plugins: [
devtools(),
{
name: 'mini-ssr',
configureServer(server) {
return () => {
server.middlewares.use(async (req, res) => {
const { render } = await server.ssrLoadModule('/entry-server.js')
res.setHeader('content-type', 'text/html')
res.end(await server.transformIndexHtml(req.url ?? '/', render()))
})
}
},
},
],
}
entry-server.js — the <html marker makes the console-pipe transform patch the server console here:
export function render() {
return '<html><body><h1>tsd console loop repro</h1><script type="module" src="/main.js"></script></body></html>'
}
main.js — the createRoot marker mimics a real client entry (which is what the client-side pipe injection keys on):
function createRoot(el) {
el.textContent = 'mounted'
}
createRoot(document.querySelector('h1'))
// A single console entry seeds the infinite echo.
console.error('seed')
Then:
pnpm install && pnpm dev- Open the page in a browser.
- Watch the terminal and the browser console.
Measured on this repro (Playwright counting console messages, no interaction after load):
| 10s | 20s | 30s | |
|---|---|---|---|
| browser console entries | 189 | 374 | 559 |
…growing without bound; the dev-server terminal shows the same nested [vite] (client) [console.error] [Server] lines.
Expected behavior
One console.error('seed') produces one line in the browser and (via Vite's forwarding) one line in the terminal. Vite-forwarded client logs should not be re-broadcast to the browser as server logs.
Screenshots or Videos
No response
Platform
- OS: macOS 15 (Darwin 25.4.0)
- Node: 22.20.0
- vite: 8.0.16 (
server.forwardConsoledefault-enabled) - @tanstack/devtools-vite: 0.8.1 (also reproduces on 0.7.0)
Additional context
We're currently carrying this as a pnpm patch — extending the recursion guard in the generated console-pipe code so the server-side patch skips Vite-forwarded client logs:
// Skip our own TSD Console Pipe logs to avoid recursion/noise
if (args.length > 0 && typeof args[0] === 'string' &&
(args[0].indexOf('[TSD Console Pipe]') !== -1 ||
args[0].indexOf('[@tanstack/devtools') !== -1)) {
return;
}
+
+ // Vite 8's server.forwardConsole re-prints CLIENT console output in
+ // the terminal ("[vite] (client) [console.warn] ..."). Those are not
+ // server logs — re-broadcasting them to the browser makes the browser
+ // log them again, which vite forwards again: an infinite
+ // client->server->client echo. Skip vite-forwarded client logs.
+ if (isServer && args.length > 0 && typeof args[0] === 'string' &&
+ (args[0].indexOf('(client)') !== -1 ||
+ args[0].indexOf('[console.') !== -1)) {
+ return;
+ }
With that guard in place: same app, same seed error — browser console stays at 5 entries (flat), terminal stays quiet, and genuine server logs still mirror into the browser once, as intended. A marker-based handshake between the pipe and Vite's forwarder would probably be more robust than string matching, but the diff above demonstrates the loop and the fix.
- Vorherrschende Sprache
- TypeScript
- Sterne
- 499
- Forks
- 100
- Ø Merge
- 1 T. 17 Std.
- Gemergte PRs (30 T.)
- 4
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus TanStack/devtools
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 45/100
-
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 74/100
Alle Issues in TanStack/devtools
Ähnliche Issues
-
bug(cli): hapi doctor inline-media prints a fabricated B:\ helper-script path in packaged installs Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
-
Crush Offen
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 85/100
catppuccin/catppuccin#3125 ·
-
Add a SECURITY.md Offen
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 90/100
ElementsProject/cln-application#167 · 1 Kommentar · 1 Reaktion ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Quantco/pnpm-licenses#17 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100