diff --git a/.changeset/old-insects-divide.md b/.changeset/old-insects-divide.md
new file mode 100644
index 0000000000..75707eb428
--- /dev/null
+++ b/.changeset/old-insects-divide.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: don't mark deriveds as clean if updating during teardown
diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js
index 21780be862..e9cea0df3e 100644
--- a/packages/svelte/src/internal/client/reactivity/deriveds.js
+++ b/packages/svelte/src/internal/client/reactivity/deriveds.js
@@ -9,7 +9,8 @@ import {
update_reaction,
increment_write_version,
set_active_effect,
- push_reaction_value
+ push_reaction_value,
+ is_destroying_effect
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
@@ -172,13 +173,18 @@ export function execute_derived(derived) {
*/
export function update_derived(derived) {
var value = execute_derived(derived);
- var status =
- (skip_reaction || (derived.f & UNOWNED) !== 0) && derived.deps !== null ? MAYBE_DIRTY : CLEAN;
-
- set_signal_status(derived, status);
if (!derived.equals(value)) {
derived.v = value;
derived.wv = increment_write_version();
}
+
+ // don't mark derived clean if we're reading it inside a
+ // cleanup function, or it will cache a stale value
+ if (is_destroying_effect) return;
+
+ var status =
+ (skip_reaction || (derived.f & UNOWNED) !== 0) && derived.deps !== null ? MAYBE_DIRTY : CLEAN;
+
+ set_signal_status(derived, status);
}
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/_config.js b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/_config.js
new file mode 100644
index 0000000000..1016ffb43e
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/_config.js
@@ -0,0 +1,22 @@
+import { test } from '../../test';
+import { flushSync } from 'svelte';
+
+export default test({
+ html: ``,
+
+ async test({ assert, target, logs }) {
+ assert.deepEqual(logs, ['up', { foo: false, bar: false }]);
+
+ const button = target.querySelector('button');
+
+ flushSync(() => button?.click());
+ assert.deepEqual(logs, [
+ 'up',
+ { foo: false, bar: false },
+ 'down',
+ { foo: false, bar: false },
+ 'up',
+ { foo: true, bar: true }
+ ]);
+ }
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/main.svelte b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/main.svelte
new file mode 100644
index 0000000000..fff81591d5
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/effect-teardown-stale-value/main.svelte
@@ -0,0 +1,14 @@
+
+
+