Repository: sveltejs/svelte (2025-08-14T17:47:00Z)

Issue:
Crash on circular reference assignment in $state with 5.34.0+
### Describe the bug

Assigning a circular reference into a `$state` object immediately crashes with a RangeError (maximum call stack size exceeded) in Svelte 5.34.0+, even without any recursion.

This worked as expected in Svelte 5.33.19.


### Reproduction

```svelte
<script>
  const filesState = $state({ files: {} });
  let nodes = { id: 1, items: [{ id: 2, items: [{ id: 3 }, { id: 4 }] }] };

  filesState.files = nodes;
  console.log('before assignment');
  filesState.files.items[0].parent = filesState.files; // 💥 crashes here
  console.log('after assignment'); // never reached
</script>
```

### Logs

```shell

```

### System Info

```shell
- Svelte version: 5.34.0 (and above)
- Previous working: 5.33.19
```

### Severity

blocking an upgrade


Discussion:
Mmm does this also happen if you run it in production mode?
> Mmm does this also happen if you run it in production mode?

Just checked and it works fine in production mode with 5.34.0 (and with latest svelte@5.38.1).

Very hard to do development though.
Yeah It was just to pinpoint the issue, I think I know what this is



Patch:
diff --git a/.changeset/six-shirts-scream.md b/.changeset/six-shirts-scream.md
new file mode 100644
index 000000000000..ac4d1834741b
--- /dev/null
+++ b/.changeset/six-shirts-scream.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: avoid recursion error when tagging circular references
diff --git a/packages/svelte/src/internal/client/proxy.js b/packages/svelte/src/internal/client/proxy.js
index 3ae4b87ed5d6..dae3791eb0c7 100644
--- a/packages/svelte/src/internal/client/proxy.js
+++ b/packages/svelte/src/internal/client/proxy.js
@@ -93,9 +93,11 @@ export function proxy(value) {
 
 	/** Used in dev for $inspect.trace() */
 	var path = '';
-
+	let updating = false;
 	/** @param {string} new_path */
 	function update_path(new_path) {
+		if (updating) return;
+		updating = true;
 		path = new_path;
 
 		tag(version, `${path} version`);
@@ -104,6 +106,7 @@ export function proxy(value) {
 		for (const [prop, source] of sources) {
 			tag(source, get_label(path, prop));
 		}
+		updating = false;
 	}
 
 	return new Proxy(/** @type {any} */ (value), {
@@ -284,13 +287,13 @@ export function proxy(value) {
 			if (s === undefined) {
 				if (!has || get_descriptor(target, prop)?.writable) {
 					s = with_parent(() => source(undefined, stack));
-					set(s, proxy(value));
-
-					sources.set(prop, s);
 
 					if (DEV) {
 						tag(s, get_label(path, prop));
 					}
+					set(s, proxy(value));
+
+					sources.set(prop, s);
 				}
 			} else {
 				has = s.v !== UNINITIALIZED;


Test patch:
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-circular-reference/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-trace-circular-reference/_config.js
new file mode 100644
index 000000000000..ca81c7854a29
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-circular-reference/_config.js
@@ -0,0 +1,26 @@
+import { test } from '../../test';
+import { normalise_trace_logs } from '../../../helpers.js';
+
+export default test({
+	compileOptions: {
+		dev: true
+	},
+
+	test({ assert, logs }) {
+		const files = { id: 1, items: [{ id: 2, items: [{ id: 3 }, { id: 4 }] }] };
+		// @ts-expect-error
+		files.items[0].parent = files;
+		assert.deepEqual(normalise_trace_logs(logs), [
+			{ log: 'test (main.svelte:5:4)' },
+			{ log: '$state', highlighted: true },
+			{ log: 'filesState.files', highlighted: false },
+			{ log: files },
+			{ log: '$state', highlighted: true },
+			{ log: 'filesState.files.items[0].parent.items', highlighted: false },
+			{ log: files.items },
+			{ log: '$state', highlighted: true },
+			{ log: 'filesState.files.items[0].parent.items[0]', highlighted: false },
+			{ log: files.items[0] }
+		]);
+	}
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-trace-circular-reference/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-trace-circular-reference/main.svelte
new file mode 100644
index 000000000000..7640d48f7705
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-trace-circular-reference/main.svelte
@@ -0,0 +1,10 @@
+<script>
+    const filesState = $state({ files: {} });
+    let nodes = { id: 1, items: [{ id: 2, items: [{ id: 3 }, { id: 4 }] }] };
+    filesState.files = nodes;
+    function test() {
+        $inspect.trace();
+        filesState.files.items[0].parent = filesState.files;
+    }
+    $effect(test);
+</script>
\ No newline at end of file