Repository: sveltejs/svelte (2025-07-21T13:19:18Z)

Issue:
$derived reactivity breaks with "createSubscriber" after its teardown/destroy has been called at least once.
### Describe the bug

The reactivity of $derived seems to break in combination with createSubscriber after its teardown has been called once AND if the value it's watching is not being used elsewhere. Using instead a plain (reactive) function seems to work fine, however.   

My apologies if this is intended behavior, and not a bug which it seems to me to be.

### Reproduction

https://svelte.dev/playground/b451d2f033f94669a5ee60537e679fd5?version=5.28.2

### Logs

```shell

```

### System Info

```shell
5.28.2
```

### Severity

annoyance
Reactivity is broken when deriving .current on fromStore and updating the store asynchronously
### Describe the bug

I can't describe the bug any more detailed that the title, but I've reduced it to a very small REPL. See repro

### Reproduction

Here is the REPL, running the latest version (as of writing this) https://svelte.dev/playground/b878ade565124e529aa8c3e73275f564?version=5.35.2

It looks like it was introduced from 5.19.5 and onwards:

[REPL for 5.19.4](https://svelte.dev/playground/b878ade565124e529aa8c3e73275f564?version=5.19.4) works
[REPL for 5.19.5](https://svelte.dev/playground/b878ade565124e529aa8c3e73275f564?version=5.19.5) doesn't work

Seems to originate from: https://github.com/sveltejs/svelte/pull/15137

Annoyingly, the issue disappears if you try to `$inspect` the misbehaving variable.

### Logs

```shell

```

### System Info

```shell
System:
    OS: Linux 6.6 Ubuntu 24.04.1 LTS 24.04.1 LTS (Noble Numbat)
    CPU: (16) x64 AMD Ryzen 9 6900HS with Radeon Graphics
    Memory: 10.53 GB / 15.25 GB
    Container: Yes
    Shell: 5.2.21 - /bin/bash
  Binaries:
    Node: 22.13.1 - ~/.local/share/node/bin/node
    npm: 10.9.2 - ~/.local/share/node/bin/npm
    pnpm: 9.15.4 - ~/.local/share/node/bin/pnpm
```

### Severity

annoyance


Discussion:
It works until [svelte 5.19.4](https://svelte.dev/playground/b451d2f033f94669a5ee60537e679fd5?version=5.19.4), starts failing with [svelte 5.19.5](https://svelte.dev/playground/b451d2f033f94669a5ee60537e679fd5?version=5.19.5).
Probably related to [this PR](https://github.com/sveltejs/svelte/pull/15137).
This is a super confusing bug. I think it is more than an annoyance. The workaround is untenable for large codebases. Just remember to carry around the original value everywhere you want to use $derived? I thought I was going crazy because I'm new to Svelte. I probably need to hold off on my Svelte library work until I know when this might be fixed. 

## v5.19.4

I have found that it wasn't fully working in 5.19.4 either. It was just broken differently. 

My data flow is like this:

```mermaid
flowchart LR
A[RxJS interval<br>createSubscriber] --> B[Getter] --> C[$derived] --> D[template]
```

When the template expression isn't rendered, in 5.19.4 it doesn't unsubscribe, so when it appears again, the interval has kept going up the whole time. 

When my data flow is like this:
```mermaid
flowchart LR
A[RxJS interval<br>createSubscriber] --> B[Getter] --> D[template]
```

it does unsubscribe when I toggle off the template expression, and when it comes back on it starts from 0. 

## v5.19.5

In v5.19.5, it's the same as described in this issue. With derived, it breaks and stops updating when it appears again. Without derived, it's fine. 
This might be related to #16090.
It isn't fixed by #16110 though.
I've been using a custom `createSubscriber` function that has been working well for my use case. The only real change is incrementing `version` where stop is called. Not sure if it's the "correct" fix, but it solves the issue where re-subscribing wouldn't trigger updates:

```js
import { tick, untrack } from "svelte";

function createSubscriber(start) {
  let subscribers = 0;
  let version = $state(0);
  let stop;

  return () => {
    if ($effect.tracking()) {
      version;

      $effect(() => {
        if (subscribers === 0) {
          stop = untrack(() => start(() => (version += 1)));
        }

        subscribers += 1;

        return () => {
          tick().then(() => {
            subscribers -= 1;

            if (subscribers === 0) {
              stop?.();
              stop = undefined;
              version += 1;
            }
          });
        };
      });
    }
  };
}
```

I was exploring this and I think the issue is with `createSubscriber` but I'm not entirely sure how to fix this...[here's](https://svelte.dev/playground/9b0b294d2e524036bd4dd1cd11f9f4a3?version=5.35.3) a different reproduction that doesn't use `fromStore` and still has the problem.

As i've explained the issue is that the derived is never updated since the version of `createSubscriber` is not updated because when it's not shown there's no subscriber to it. However this means that when `check_dirtiness` runs the `wv` of the derived is bigger than the `version` of `createSubscriber` so the derived it's considered `CLEAN`
Is this a duplicate of #15888?
> Is this a duplicate of [#15888](https://github.com/sveltejs/svelte/issues/15888)?

It is...mmm, now I'm thorned if we should keep this open so that it's higher up or close this and move the context there (but then we need to remember to work on it)



Patch:
diff --git a/.changeset/fresh-penguins-impress.md b/.changeset/fresh-penguins-impress.md
new file mode 100644
index 000000000000..35ff4f0aaa29
--- /dev/null
+++ b/.changeset/fresh-penguins-impress.md
@@ -0,0 +1,5 @@
+---
+'svelte': patch
+---
+
+fix: ensure subscriptions are picked up correctly by deriveds
diff --git a/packages/svelte/src/reactivity/create-subscriber.js b/packages/svelte/src/reactivity/create-subscriber.js
index 4dcac4e6f61b..6f9313eb0a82 100644
--- a/packages/svelte/src/reactivity/create-subscriber.js
+++ b/packages/svelte/src/reactivity/create-subscriber.js
@@ -82,6 +82,10 @@ export function createSubscriber(start) {
 						if (subscribers === 0) {
 							stop?.();
 							stop = undefined;
+							// Increment the version to ensure any dependent deriveds are marked dirty when the subscription is picked up again later.
+							// If we didn't do this then the comparison of write versions would determine that the derived has a later version than
+							// the subscriber, and it would not be re-run.
+							increment(version);
 						}
 					});
 				};


Test patch:
diff --git a/packages/svelte/tests/runtime-runes/samples/store-inside-derived/_config.js b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/_config.js
new file mode 100644
index 000000000000..de078b1e7544
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/_config.js
@@ -0,0 +1,45 @@
+import { flushSync } from 'svelte';
+import { test } from '../../test';
+
+export default test({
+	test: ({ assert, target }) => {
+		const [loading, increment] = target.querySelectorAll('button');
+
+		assert.htmlEqual(
+			target.innerHTML,
+			`
+				<div>$value: 0</div>
+				<div>valueFromStore.current: 0</div>
+				<div>valueDerivedCurrent: 0</div>
+				<button>Loading</button>
+				<button>Increment</button>
+			`
+		);
+
+		loading.click();
+		flushSync();
+		assert.htmlEqual(
+			target.innerHTML,
+			`
+				<div>$value: Loading...</div>
+				<div>valueFromStore.current: Loading...</div>
+				<div>valueDerivedCurrent: Loading...</div>
+				<button>Loading</button>
+				<button>Increment</button>
+			`
+		);
+
+		increment.click();
+		flushSync();
+		assert.htmlEqual(
+			target.innerHTML,
+			`
+				<div>$value: 1</div>
+				<div>valueFromStore.current: 1</div>
+				<div>valueDerivedCurrent: 1</div>
+				<button>Loading</button>
+				<button>Increment</button>
+			`
+		);
+	}
+});
diff --git a/packages/svelte/tests/runtime-runes/samples/store-inside-derived/main.svelte b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/main.svelte
new file mode 100644
index 000000000000..06d0a0d4b4d2
--- /dev/null
+++ b/packages/svelte/tests/runtime-runes/samples/store-inside-derived/main.svelte
@@ -0,0 +1,36 @@
+<script>
+  import { fromStore, writable } from 'svelte/store';
+	
+  let isLoading = $state(false);
+
+  const value = writable(0);
+  const valueFromStore = fromStore(value);
+  const valueDerivedCurrent = $derived(valueFromStore.current);
+</script>
+
+<div>
+	$value: {isLoading ? 'Loading...' : $value}
+</div>
+
+<div>
+	valueFromStore.current: {isLoading ? 'Loading...' : valueFromStore.current}
+</div>
+
+<div>
+	valueDerivedCurrent: {isLoading ? 'Loading...' : valueDerivedCurrent}
+</div>
+
+<button
+	onclick={() => {
+		isLoading = true;
+	}}>
+	Loading
+</button>
+
+<button
+	onclick={() => {
+		$value++;
+		isLoading = false;
+	}}>
+	Increment
+</button>