From d9a5cc1802e879ec058acfa196c096b204ea4e1e Mon Sep 17 00:00:00 2001 From: rabindra789 Date: Thu, 9 Jul 2026 13:14:53 +0530 Subject: [PATCH] fix: prevent derived connection leak in untracked contexts (#18501) When a is only read inside an untracked context such as (()), is_updating_effect was causing should_connect to return true even though the active reaction (b) is not connected. This caused derived (a) to be spuriously marked CONNECTED and added to its dependencies' reactions arrays. When the component is destroyed, nothing removes these stale deriveds from their deps' reactions, creating a memory leak that retains the entire component scope. Removing is_updating_effect from the should_connect condition ensures that a derived is only connected when read by a reaction that is actually part of the reactive graph. A disconnected derived reading another derived should not cause the inner derived to connect. --- packages/svelte/src/internal/client/runtime.js | 2 +- packages/svelte/tests/signals/test.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 227203523e..87e884a347 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -665,7 +665,7 @@ export function get(signal) { (derived.f & CONNECTED) === 0 && !untracking && active_reaction !== null && - (is_updating_effect || (active_reaction.f & CONNECTED) !== 0); + (active_reaction.f & CONNECTED) !== 0; var is_new = (derived.f & REACTION_RAN) === 0; diff --git a/packages/svelte/tests/signals/test.ts b/packages/svelte/tests/signals/test.ts index 927ce2e665..07f79bd395 100644 --- a/packages/svelte/tests/signals/test.ts +++ b/packages/svelte/tests/signals/test.ts @@ -1503,4 +1503,22 @@ describe('signals', () => { assert.deepEqual(log, ['inner destroyed', 'inner destroyed']); }; }); + + test('derived read in an untracked context should not leak in deps reactions', () => { + return () => { + let s = state('hello'); + let a = derived(() => $.get(s)); + let b = derived(() => $.get(a)); + + let destroy = effect_root(() => { + $.get(b); + }); + + destroy(); + + // a was spuriously added to s.reactions via is_updating_effect + // even though the entire derived chain was read in an untracked context + assert.equal(s.reactions, null); + }; + }); });