Repository: sveltejs/svelte (2025-07-19T18:53:20Z)

Issue:
asyn svelte ReferenceError: $$ownership_validator is not defined
### Describe the bug

issue with shadcn-svelte 's dialog component, if  `bind `is removed then it works -` error $$ownership_validator is not defined`

### Reproduction

https://stackblitz.com/edit/sveltejs-kit-template-default-dc4cvgmz?file=src%2Froutes%2F%2Blayout.svelte

```svelte
<script lang="ts">
  import { Dialog, Label, Separator } from "bits-ui";
		let {open=$bindable(false)}=$props()
		const module = $derived(
	await	(async function(){return 123})()
	);
</script>
  {module}
<Dialog.Root bind:open={open}>
  <Dialog.Trigger
    class="rounded-input bg-dark text-background
	  shadow-mini hover:bg-dark/95 focus-visible:ring-foreground focus-visible:ring-offset-background focus-visible:outline-hidden
	  inline-flex h-12 items-center justify-center whitespace-nowrap px-[21px] text-[15px] font-semibold transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 active:scale-[0.98]"
  >
    New API key
  </Dialog.Trigger>
  <Dialog.Portal>
    <Dialog.Overlay
      class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80"
    />
  </Dialog.Portal>
</Dialog.Root>
```

### Logs

```shell

```

### System Info

```shell
svelte@5.36.8
```

### Severity

blocking an upgrade


Discussion:

Patch:
diff --git a/.changeset/healthy-carpets-deny.md b/.changeset/healthy-carpets-deny.md
new file mode 100644
index 000000000000..94ee865fe7b5
--- /dev/null
+++ b/.changeset/healthy-carpets-deny.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: move ownership validation into async component body
diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
index c42d1b95d88d..7f25e6c0d265 100644
--- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
+++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js
@@ -364,6 +364,12 @@ export function client_component(analysis, options) {
 			: b.stmt(b.call('$.init', analysis.immutable ? b.true : undefined))
 	]);
 
+	if (analysis.needs_mutation_validation) {
+		component_block.body.unshift(
+			b.var('$$ownership_validator', b.call('$.create_ownership_validator', b.id('$$props')))
+		);
+	}
+
 	const should_inject_context =
 		dev ||
 		analysis.needs_context ||
@@ -434,12 +440,6 @@ export function client_component(analysis, options) {
 		);
 	}
 
-	if (analysis.needs_mutation_validation) {
-		component_block.body.unshift(
-			b.var('$$ownership_validator', b.call('$.create_ownership_validator', b.id('$$props')))
-		);
-	}
-
 	// we want the cleanup function for the stores to run as the very last thing
 	// so that it can effectively clean up the store subscription even after the user effects runs
 	if (should_inject_context) {


Test patch:
diff --git a/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/Child.svelte b/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/Child.svelte
new file mode 100644
index 000000000000..d4d5cf755462
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/Child.svelte
@@ -0,0 +1,7 @@
+<script>
+	let { object } = $props();
+
+	await 1;
+</script>
+
+<button onclick={() => object.count++}>clicks: {object.count}</button>
diff --git a/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/_config.js b/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/_config.js
new file mode 100644
index 000000000000..167eee8488ae
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/_config.js
@@ -0,0 +1,22 @@
+import { tick } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+	compileOptions: {
+		dev: true
+	},
+
+	async test({ assert, target, warnings }) {
+		await tick();
+
+		const [button] = target.querySelectorAll('button');
+
+		button.click();
+		await tick();
+
+		assert.htmlEqual(target.innerHTML, '<button>clicks: 1</button>');
+		assert.deepEqual(warnings, [
+			'Mutating unbound props (`object`, at Child.svelte:7:23) is strongly discouraged. Consider using `bind:object={...}` in main.svelte (or using a callback) instead'
+		]);
+	}
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/main.svelte b/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/main.svelte
new file mode 100644
index 000000000000..ae6b43cbb14c
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/async-ownership-validation/main.svelte
@@ -0,0 +1,13 @@
+<script>
+	import Child from './Child.svelte';
+
+	let object = $state({ count: 0 });
+</script>
+
+<svelte:boundary>
+	<Child {object} />
+
+	{#snippet pending()}
+		<p>loading...</p>
+	{/snippet}
+</svelte:boundary>