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
Running svelte-check --tsgo (or --incremental) while vite dev is running makes Vite treat the run as a TypeScript config change:
[vite] changed tsconfig file detected: /path/to/app/.svelte-kit/.svelte-check/tsconfig.json - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.
Vite then calls moduleGraph.invalidateAll() on every environment and sends a full-reload to every open tab. Any SSR render that is in flight at that moment fails with a 500 and Svelte error: lifecycle_outside_component (full trace under Logs). Requests that start after the invalidation are fine, so it looks like a random 500 rather than a broken server.
Why it happens. Three pieces, none wrong on its own:
Vite (packages/vite/src/node/plugins/esbuild.ts, reloadOnTsconfigChange) reacts to any watched file whose path ends in /tsconfig.json, anywhere under the root, with invalidateAll() + full-reload. It runs from the watcher's change/add/unlink handlers, so server.watch.ignored is the effective gate.
SvelteKit's ignore glob does not cover nested files in outDir.packages/kit/src/exports/vite/index.js:
watch: {ignored: [// Ignore all siblings of config.kit.outDir/generated`${out_dir}/!(generated)`]}
!(generated) is a single-segment extglob. It matches .svelte-kit/tsconfig.json and the directory.svelte-kit/.svelte-check, but not .svelte-kit/.svelte-check/tsconfig.json (nor .svelte-kit/types/**). Checked with picomatch and chokidar's { dot: true }:
On macOS chokidar uses one recursive fsevents watch and filters each event by its own path (FsEventsHandler.checkIgnored); a directory-level match only extends to descendants once an event for the directory itself is seen with directory stats. .svelte-check/ already exists when vite dev starts, so the file event passes straight through to Vite. (Read from the bundled chokidar source. On Linux the nodefs handler never descends into an ignored directory, so it may not reproduce there; that is a difference in chokidar handlers, not in the glob.)
Why the 500. The ?v=72a77cde stamp in the trace is the SSR dependency optimizer's hash (vite-plugin-svelte prebundles the svelte packages for the ssr environment too). A request has already evaluated svelte/internal/server and started rendering; invalidateAll() clears the module graph underneath it; the next module the render imports (here svelte-sonner's Toaster) is evaluated fresh against a new instance of svelte/internal/server. component() pushed the current component in the old instance, setContext reads it in the new one and finds nothing.
The race is easy to hit in a normal svelte-kit sync && svelte-check run: sync rewrites .svelte-kit/generated/*, which triggers a page reload, the open tab re-requests the page, and about a second later svelte-check writes its tsconfig and Vite invalidates everything under that render. In a real-sized app, where a page render takes a few seconds, the tab's reload request is nearly always still in flight.
Proposed fix (kit). Kit owns both outDir and the ignore list, and the comment already states the intent ("ignore all siblings of generated"). A second entry makes it cover nested paths on every platform:
watch: {ignored: [// Ignore all siblings of config.kit.outDir/generated, at any depth`${out_dir}/!(generated)`,`${out_dir}/!(generated)/**`]}
Verified in the reproduction: with that entry in server.watch.ignored, svelte-check --tsgo against the running dev server no longer logs the tsconfig change, reloads, or fails the in-flight request; the control run without it does.
Alternatives, if you would rather see it fixed elsewhere: svelte-check could name the overlay anything other than tsconfig.json (Vite keys on endsWith('/tsconfig.json')) or skip the write when the content is unchanged, but kit's glob would still be leaky for any other tooling writing into .svelte-kit. Vite could narrow the reload to tsconfigs that tsconfck actually resolved for a module (vitejs/vite#22588 goes partway but keeps the full reload).
Happy to open a PR for the kit change if you agree with the direction.
Minimal SvelteKit project (sv create --template minimal --types ts) plus svelte-sonner's <Toaster /> in +layout.svelte and a +page.server.ts whose load sleeps 4s (stands in for a real page's render time and opens the window for the race).
bun install
npx svelte-check --tsconfig ./tsconfig.json --tsgo # creates .svelte-kit/.svelte-check/
npx vite dev --port 5173
Expected 200, actual 500 plus the log below. Every time, on macOS, under both Node 25 and Bun 1.4. Uncomment the server.watch.ignored line in the repro's vite.config.ts and the same steps print 200 with no reload. Without the artificial delay the render finishes before the invalidation and only the spurious full reload remains, which is why a trivially small app does not show the 500.
Logs
4:56:05 PM [vite] changed tsconfig file detected: /path/to/repro/.svelte-kit/.svelte-check/tsconfig.json - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.
Svelte error: lifecycle_outside_component
`setContext(...)` can only be used during component initialisation
https://svelte.dev/e/lifecycle_outside_component
at lifecycle_outside_component (node_modules/svelte/src/internal/shared/errors.js:78:17)
Same thing in the real app where we first saw it. Note the request that fails is the one whose render straddles the tsconfig message (5s render, started at :54, returned at :59); the request 5s later is fine:
3:54:53 PM [vite] (client) page reload .svelte-kit/generated/client/nodes/0.js
3:54:53 PM [vite] (ssr) page reload .svelte-kit/generated/root.svelte
3:54:54 PM [vite] changed tsconfig file detected: /path/to/app/.svelte-kit/.svelte-check/tsconfig.json - Clearing cache and forcing full-reload to ensure TypeScript is compiled with updated config values.
Svelte error: lifecycle_outside_component
`setContext(...)` can only be used during component initialisation
https://svelte.dev/e/lifecycle_outside_component
at lifecycle_outside_component (node_modules/svelte/src/internal/shared/errors.js:78:21)
at get_or_init_context_map (node_modules/svelte/src/internal/shared/context.js:48:3)
at setContext (node_modules/svelte/src/internal/server/context.js:43:2)
at <anonymous> (node_modules/svelte-sonner/dist/Toaster.svelte:383:19)
at child (node_modules/svelte/src/internal/server/renderer.js:214:18)
at component (node_modules/svelte/src/internal/server/renderer.js:319:22)
at Toaster (node_modules/svelte-sonner/dist/Toaster.svelte?v=72a77cde:75:22)
at <anonymous> (src/lib/components/ui/sonner/sonner.svelte:70:38)
at child (node_modules/svelte/src/internal/server/renderer.js:214:18)
at component (node_modules/svelte/src/internal/server/renderer.js:319:22)
[15:54:59.201] INFO: request GET /platform-requests/<id> status: 500 durationMs: 5091
[15:55:04.524] INFO: request GET /login status: 200 durationMs: 2813
System Info
System:
OS: macOS 15.7.5
CPU: (10) arm64 Apple M4
Memory: 304.20 MB / 24.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 25.6.1 - /opt/homebrew/bin/node
npm: 11.9.0 - /opt/homebrew/bin/npm
pnpm: 10.32.1 - /opt/homebrew/bin/pnpm
bun: 1.4.2 - /opt/homebrew/bin/bun
Deno: 2.7.4 - /opt/homebrew/bin/deno
npmPackages:
@sveltejs/adapter-auto: ^7.0.1 => 7.0.1
@sveltejs/kit: ^2.70.3 => 2.70.3
@sveltejs/vite-plugin-svelte: ^7.1.2 => 7.3.0
svelte: 5.56.10 => 5.56.10
svelte-check: ^4.7.6 => 4.7.6
svelte-sonner: ^1.2.1 => 1.2.1
vite: 8.2.2 => 8.2.2
(kit `main` / 3.0.0-next carries the same `${out_dir}/!(generated)` glob, so it is affected too. Also reproduced with vite 8.3.0 and svelte 5.57.0.)
Disclosure: the investigation, root-cause analysis, reproduction project and this write-up were done by Claude (Fable 5.1, Anthropic) working in Claude Code. I reproduced the failure in my own app with the open tab and confirmed the 500 / 200 results of the repro steps and the workaround myself.
Describe the bug
Running
svelte-check --tsgo(or--incremental) whilevite devis running makes Vite treat the run as a TypeScript config change:Vite then calls
moduleGraph.invalidateAll()on every environment and sends afull-reloadto every open tab. Any SSR render that is in flight at that moment fails with a 500 andSvelte error: lifecycle_outside_component(full trace under Logs). Requests that start after the invalidation are fine, so it looks like a random 500 rather than a broken server.Why it happens. Three pieces, none wrong on its own:
svelte-check (
packages/svelte-check/src/incremental.ts, since feat: svelte-check--incremental/--tsgolanguage-tools#2932) writes its overlay to.svelte-kit/.svelte-check/tsconfig.jsonand rewrites it unconditionally on every run. Maintainers there consider the location intended (--incrementalsaves.svelte-checkwithin.svelte-kitlanguage-tools#2941).Vite (
packages/vite/src/node/plugins/esbuild.ts,reloadOnTsconfigChange) reacts to any watched file whose path ends in/tsconfig.json, anywhere under the root, withinvalidateAll()+full-reload. It runs from the watcher'schange/add/unlinkhandlers, soserver.watch.ignoredis the effective gate.SvelteKit's ignore glob does not cover nested files in
outDir.packages/kit/src/exports/vite/index.js:!(generated)is a single-segment extglob. It matches.svelte-kit/tsconfig.jsonand the directory.svelte-kit/.svelte-check, but not.svelte-kit/.svelte-check/tsconfig.json(nor.svelte-kit/types/**). Checked with picomatch and chokidar's{ dot: true }:On macOS chokidar uses one recursive fsevents watch and filters each event by its own path (
FsEventsHandler.checkIgnored); a directory-level match only extends to descendants once an event for the directory itself is seen with directory stats..svelte-check/already exists whenvite devstarts, so the file event passes straight through to Vite. (Read from the bundled chokidar source. On Linux the nodefs handler never descends into an ignored directory, so it may not reproduce there; that is a difference in chokidar handlers, not in the glob.)Why the 500. The
?v=72a77cdestamp in the trace is the SSR dependency optimizer's hash (vite-plugin-svelte prebundles thesveltepackages for the ssr environment too). A request has already evaluatedsvelte/internal/serverand started rendering;invalidateAll()clears the module graph underneath it; the next module the render imports (heresvelte-sonner's Toaster) is evaluated fresh against a new instance ofsvelte/internal/server.component()pushed the current component in the old instance,setContextreads it in the new one and finds nothing.The race is easy to hit in a normal
svelte-kit sync && svelte-checkrun:syncrewrites.svelte-kit/generated/*, which triggers a page reload, the open tab re-requests the page, and about a second later svelte-check writes its tsconfig and Vite invalidates everything under that render. In a real-sized app, where a page render takes a few seconds, the tab's reload request is nearly always still in flight.Proposed fix (kit). Kit owns both
outDirand the ignore list, and the comment already states the intent ("ignore all siblings ofgenerated"). A second entry makes it cover nested paths on every platform:picomatch (
{ dot: true }) with both entries:Verified in the reproduction: with that entry in
server.watch.ignored,svelte-check --tsgoagainst the running dev server no longer logs the tsconfig change, reloads, or fails the in-flight request; the control run without it does.Alternatives, if you would rather see it fixed elsewhere: svelte-check could name the overlay anything other than
tsconfig.json(Vite keys onendsWith('/tsconfig.json')) or skip the write when the content is unchanged, but kit's glob would still be leaky for any other tooling writing into.svelte-kit. Vite could narrow the reload to tsconfigs that tsconfck actually resolved for a module (vitejs/vite#22588 goes partway but keeps the full reload).Happy to open a PR for the kit change if you agree with the direction.
Reproduction
https://github.com/jose-manuel-silva/sveltekit-svelte-check-tsgo-reload
Minimal SvelteKit project (
sv create --template minimal --types ts) plussvelte-sonner's<Toaster />in+layout.svelteand a+page.server.tswhoseloadsleeps 4s (stands in for a real page's render time and opens the window for the race).bun install npx svelte-check --tsconfig ./tsconfig.json --tsgo # creates .svelte-kit/.svelte-check/ npx vite dev --port 5173Second terminal:
Expected
200, actual500plus the log below. Every time, on macOS, under both Node 25 and Bun 1.4. Uncomment theserver.watch.ignoredline in the repro'svite.config.tsand the same steps print200with no reload. Without the artificial delay the render finishes before the invalidation and only the spurious full reload remains, which is why a trivially small app does not show the 500.Logs
System Info
System: OS: macOS 15.7.5 CPU: (10) arm64 Apple M4 Memory: 304.20 MB / 24.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 25.6.1 - /opt/homebrew/bin/node npm: 11.9.0 - /opt/homebrew/bin/npm pnpm: 10.32.1 - /opt/homebrew/bin/pnpm bun: 1.4.2 - /opt/homebrew/bin/bun Deno: 2.7.4 - /opt/homebrew/bin/deno npmPackages: @sveltejs/adapter-auto: ^7.0.1 => 7.0.1 @sveltejs/kit: ^2.70.3 => 2.70.3 @sveltejs/vite-plugin-svelte: ^7.1.2 => 7.3.0 svelte: 5.56.10 => 5.56.10 svelte-check: ^4.7.6 => 4.7.6 svelte-sonner: ^1.2.1 => 1.2.1 vite: 8.2.2 => 8.2.2 (kit `main` / 3.0.0-next carries the same `${out_dir}/!(generated)` glob, so it is affected too. Also reproduced with vite 8.3.0 and svelte 5.57.0.)Severity
serious, but I can work around it
Additional Information
Workaround, in
vite.config.ts:Related:
.svelte-kittrigger reloads" issue that led to the current glob--incremental/--tsgolanguage-tools#2932 — introduced--incremental/--tsgoand the.svelte-kit/.svelte-checkcache dir--incrementalsaves.svelte-checkwithin.svelte-kitlanguage-tools#2941 — maintainers confirming.svelte-kit/.svelte-checkis the intended locationmode)vi.resetModules()causeslifecycle_outside_componenterror in svelte@5.39.0 svelte#16832 — samelifecycle_outside_componentmechanism (two runtime copies) triggered from vitest'sresetModulesDisclosure: the investigation, root-cause analysis, reproduction project and this write-up were done by Claude (Fable 5.1, Anthropic) working in Claude Code. I reproduced the failure in my own app with the open tab and confirmed the 500 / 200 results of the repro steps and the workaround myself.