sveltejs/svelte · warning · MigrationError

can't migrate `${...}` to `$${rune}` because there's a varia

Error message

can't migrate `${...}` to `$${rune}` because there's a variable named ${rune}.
     Rename the variable and try again or migrate by hand.

What it means

Thrown by the reactive-declaration migrator when converting a `let` declaration into `$state`/`$derived` would collide with an in-scope variable already named `state` or `derived`. Same shape as the props-rune collision (error 1) but for the instance-script state transform. The offending source slice is included in the message via `state.str.original.substring(...)`.

Source

Thrown at packages/svelte/src/compiler/migrate/index.js:717

							);
						}
					}
				} else {
					state.props_insertion_point = /** @type {number} */ (declarator.end);
				}

				state.str.update(start, end, '');

				continue;
			}

			/**
			 * @param {"state"|"derived"} rune
			 */
			function check_rune_binding(rune) {
				const has_rune_binding = !!state.scope.get(rune);
				if (has_rune_binding) {
					throw new MigrationError(
						`can't migrate \`${state.str.original.substring(/** @type {number} */ (node.start), node.end)}\` to \`$${rune}\` because there's a variable named ${rune}.\n     Rename the variable and try again or migrate by hand.`
					);
				}
			}

			// state
			if (declarator.init) {
				let { start, end } = /** @type {{ start: number, end: number }} */ (declarator.init);

				if (declarator.init.type === 'SequenceExpression') {
					while (state.str.original[start] !== '(') start -= 1;
					while (state.str.original[end - 1] !== ')') end += 1;
				}

				check_rune_binding('state');

				state.str.prependLeft(start, '$state(');
				state.str.appendRight(end, ')');

View on GitHub (pinned to 20b341f100)

Solutions

  1. Rename the offending `state`/`derived` variable to something else (e.g. `currentState`, `memo`) and re-run migrate.
  2. Migrate the file by hand and apply runes directly without renaming.
  3. Pre-scan for `\b(state|derived)\b` declarations before running the codemod.

Example fix

// before
<script>
  let state = $: computeInitial();
  let count = 0;
  $: doubled = count * 2;
</script>

// after (rename, then migrate)
<script>
  let currentState = computeInitial();
  let count = $state(0);
  let doubled = $derived(count * 2);
</script>
Defensive patterns

Strategy: validation

Validate before calling

// Detect instance-script state/derived identifier collisions.
function hasStateNameCollision(source) {
  const script = source.match(/<script\b[^>]*>([\s\S]*?)<\/script>/)?.[1] ?? '';
  return /\b(let|const|var)\s+(state|derived)\b/.test(script);
}
if (hasStateNameCollision(componentSource)) {
  // rename before migrating
}

Try / catch

const { code } = migrate({ filename, source });
if (code.startsWith('<!-- @migration-task') && /because there's a variable named (state|derived)/i.test(code)) {
  // rename the offending variable and re-run, or hand-migrate
}

Prevention

When it happens

Trigger: Running `migrate()` on a Svelte 4 component whose `<script>` declares `let state = ...` or `let derived = ...` (or otherwise introduces such a binding into the component scope), and the codemod needs to emit `$state(...)` or `$derived(...)` for another declaration.

Common situations: State-machine or XState-style components where `state` is a natural variable name; computed-cache components that name a memo `derived`. The collision is purely lexical — the rune name clashes with the user identifier.

Related errors


AI-assisted analysis of sveltejs/svelte@20b341f100 (2026-08-12). Data as JSON: /api/errors/5c8e572a916be686. Report an issue: GitHub.