diff --git a/.changeset/cool-trains-yawn.md b/.changeset/cool-trains-yawn.md
new file mode 100644
index 000000000000..d5198d118c52
--- /dev/null
+++ b/.changeset/cool-trains-yawn.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure internal cloning can work circular values
diff --git a/packages/svelte/src/internal/shared/clone.js b/packages/svelte/src/internal/shared/clone.js
index bfdc9af26390..cef14bc14f9e 100644
--- a/packages/svelte/src/internal/shared/clone.js
+++ b/packages/svelte/src/internal/shared/clone.js
@@ -49,9 +49,10 @@ export function snapshot(value, skip_warning = false) {
  * @param {Map<T, Snapshot<T>>} cloned
  * @param {string} path
  * @param {string[]} paths
+ * @param {null | T} original The original value, if `value` was produced from a `toJSON` call
  * @returns {Snapshot<T>}
  */
-function clone(value, cloned, path, paths) {
+function clone(value, cloned, path, paths, original = null) {
 	if (typeof value === 'object' && value !== null) {
 		const unwrapped = cloned.get(value);
 		if (unwrapped !== undefined) return unwrapped;
@@ -63,6 +64,10 @@ function clone(value, cloned, path, paths) {
 			const copy = /** @type {Snapshot<any>} */ ([]);
 			cloned.set(value, copy);
 
+			if (original !== null) {
+				cloned.set(original, copy);
+			}
+
 			for (let i = 0; i < value.length; i += 1) {
 				copy.push(clone(value[i], cloned, DEV ? `${path}[${i}]` : path, paths));
 			}
@@ -75,6 +80,10 @@ function clone(value, cloned, path, paths) {
 			const copy = {};
 			cloned.set(value, copy);
 
+			if (original !== null) {
+				cloned.set(original, copy);
+			}
+
 			for (var key in value) {
 				// @ts-expect-error
 				copy[key] = clone(value[key], cloned, DEV ? `${path}.${key}` : path, paths);
@@ -92,7 +101,9 @@ function clone(value, cloned, path, paths) {
 				/** @type {T & { toJSON(): any } } */ (value).toJSON(),
 				cloned,
 				DEV ? `${path}.toJSON()` : path,
-				paths
+				paths,
+				// Associate the instance with the toJSON clone
+				value
 			);
 		}
 	}
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js
new file mode 100644
index 000000000000..ab496971955f
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/_config.js
@@ -0,0 +1,23 @@
+import { test } from '../../test';
+
+export default test({
+	compileOptions: {
+		dev: true
+	},
+
+	async test({ assert, logs }) {
+		var a = {
+			a: {}
+		};
+		a.a = a;
+
+		var b = {
+			a: {
+				b: {}
+			}
+		};
+		b.a.b = b;
+
+		assert.deepEqual(logs, ['init', a, 'init', b]);
+	}
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte
new file mode 100644
index 000000000000..f7874d2192ce
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/inspect-recursive-2/main.svelte
@@ -0,0 +1,23 @@
+<script>
+	class A {
+		toJSON(){
+			return {
+				a: this
+			}
+		}
+	}
+	const state = $state(new A());
+	$inspect(state);
+
+	class B {
+		toJSON(){
+			return {
+				a: {
+					b: this
+				}
+			}
+		}
+	}
+	const state2 = $state(new B());
+	$inspect(state2);
+</script>