Skip to content

vite.config change during an in-flight config restart is silently dropped, leaving the server running a config that no longer matches disk #23392

Description

@silentoplayz

Describe the bug

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.

async restart(forceOptimize?: boolean) {
  if (!server._restartPromise) {
    server._forceOptimizeOnRestart = !!forceOptimize
    server._restartPromise = restartServer(server).finally(() => {
      server._restartPromise = null
      server._forceOptimizeOnRestart = false
    })
  }
  return server._restartPromise
}

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.

Reproduction

https://github.com/silentoplayz/vite-restart-race-repro

Steps to reproduce

npm install
npm run repro

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:

  1. Confirms /api returns the backend JSON through the proxy.
  2. Overwrites vite.config.ts with a variant without the proxy (restart Won't compile with img path starting with ./ #1 begins).
  3. One second later, while restart Won't compile with img path starting with ./ #1 is still in flight, restores the config with the proxy.
  4. 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.
  5. As a control, a single touch vite.config.ts restarts correctly and /api works 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.2

Used Package Manager

npm

Logs

Full vite log from the reproduction run
  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.

Validations

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    p2-edge-caseBug, but has workaround or limited in scope (priority)

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions