You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When vite.config.ts changes, the dev server restarts itself. If the config file changes again while that restart is still in flight, the second change is silently dropped: server.restart() guards with _restartPromise and returns the in-flight promise instead of queueing a follow-up restart.
The dropped call represented a newer config on disk. After the in-flight restart completes, the server is running a config snapshot that no longer matches vite.config.ts, and since the file does not change again, nothing ever triggers the missing restart. The server stays wrong until the developer intervenes (touch the config file, press r, or kill and relaunch).
The window is the full duration of restartServer(), which includes a complete _createServer(): config resolution, plugin init, framework plugin startup. With a framework plugin like SvelteKit this is multiple seconds, so it is easy to hit in practice. The common real-world trigger is git checkout between branches whose vite.config.ts differ: switching to an older branch and back within a few seconds leaves the server running the older branch's config while disk has the newer one. I hit this repeatedly in the Open WebUI repo, where a recently added server.proxy block would silently vanish after branch switching, killing every /api request until a manual restart.
The log shows the drop clearly, two config change notices but only one completed restart:
4:02:06 AM [vite] vite.config.ts changed, restarting server...
4:02:07 AM [vite] vite.config.ts changed, restarting server...
4:02:09 AM [vite] server restarted.
What I expect: a restart() requested while a restart is in flight should be coalesced into one follow-up restart after the current one completes (a pending flag would do), so the server always converges to the config on disk.
For context, #21636 and #21810 recently hardened this guard by closing loopholes that allowed a second concurrent restart to start. That made the drop airtight, but the dropped request can represent a newer config, which is the case this report covers.
The script starts a tiny JSON backend on :9876 and vite dev on :5199 with a config that proxies /api to the backend. The config includes a plugin whose config() hook awaits 3 seconds, simulating slow framework plugin startup so the race window is deterministic. The script then:
Confirms /api returns the backend JSON through the proxy.
After everything settles, vite.config.ts on disk contains the proxy, but /api returns the SPA fallback HTML: the server is running the proxy-less snapshot.
As a control, a single touch vite.config.ts restarts correctly and /api works again.
VITE v8.2.2 ready in 3093 ms
➜ Local: http://localhost:5199/
➜ Network: use --host to expose
4:02:06 AM [vite] vite.config.ts changed, restarting server...
4:02:07 AM [vite] vite.config.ts changed, restarting server...
4:02:09 AM [vite] server restarted.
4:02:15 AM [vite] vite.config.ts changed, restarting server...
4:02:18 AM [vite] server restarted.
Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
Make sure this is a Vite issue and not a framework-specific issue. For example, if it's a Vue SFC related bug, it should likely be reported to vuejs/core instead.
Describe the bug
When
vite.config.tschanges, the dev server restarts itself. If the config file changes again while that restart is still in flight, the second change is silently dropped:server.restart()guards with_restartPromiseand returns the in-flight promise instead of queueing a follow-up restart.The dropped call represented a newer config on disk. After the in-flight restart completes, the server is running a config snapshot that no longer matches
vite.config.ts, and since the file does not change again, nothing ever triggers the missing restart. The server stays wrong until the developer intervenes (touch the config file, pressr, or kill and relaunch).The window is the full duration of
restartServer(), which includes a complete_createServer(): config resolution, plugin init, framework plugin startup. With a framework plugin like SvelteKit this is multiple seconds, so it is easy to hit in practice. The common real-world trigger isgit checkoutbetween branches whosevite.config.tsdiffer: switching to an older branch and back within a few seconds leaves the server running the older branch's config while disk has the newer one. I hit this repeatedly in the Open WebUI repo, where a recently addedserver.proxyblock would silently vanish after branch switching, killing every/apirequest until a manual restart.The log shows the drop clearly, two config change notices but only one completed restart:
What I expect: a
restart()requested while a restart is in flight should be coalesced into one follow-up restart after the current one completes (a pending flag would do), so the server always converges to the config on disk.For context, #21636 and #21810 recently hardened this guard by closing loopholes that allowed a second concurrent restart to start. That made the drop airtight, but the dropped request can represent a newer config, which is the case this report covers.
Reproduction
https://github.com/silentoplayz/vite-restart-race-repro
Steps to reproduce
The script starts a tiny JSON backend on
:9876andvite devon:5199with a config that proxies/apito the backend. The config includes a plugin whoseconfig()hook awaits 3 seconds, simulating slow framework plugin startup so the race window is deterministic. The script then:/apireturns the backend JSON through the proxy.vite.config.tswith a variant without the proxy (restart Won't compile with img path starting with ./ #1 begins).vite.config.tson disk contains the proxy, but/apireturns the SPA fallback HTML: the server is running the proxy-less snapshot.touch vite.config.tsrestarts correctly and/apiworks again.System Info
System: OS: Linux 7.1 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat) CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12700K Memory: 34.63 GB / 62.65 GB Container: Yes Shell: 5.9 - /usr/bin/zsh Binaries: Node: 22.17.0 - /home/g30/.nvm/versions/node/v22.17.0/bin/node npm: 11.4.2 - /home/g30/.nvm/versions/node/v22.17.0/bin/npm Browsers: Chrome: 152.0.7977.64 Firefox: 153.0.4 Firefox Developer Edition: 153.0.4 npmPackages: vite: ^8.2.2 => 8.2.2Used Package Manager
npm
Logs
Full vite log from the reproduction run
Validations