diff --git a/packages/reactivity/__tests__/watch.spec.ts b/packages/reactivity/__tests__/watch.spec.ts
index 245acfd63be..25d752c96a3 100644
--- a/packages/reactivity/__tests__/watch.spec.ts
+++ b/packages/reactivity/__tests__/watch.spec.ts
@@ -5,6 +5,7 @@ import {
   type WatchOptions,
   type WatchScheduler,
   computed,
+  onScopeDispose,
   onWatcherCleanup,
   ref,
   watch,
@@ -277,4 +278,24 @@ describe('watch', () => {
 
     expect(dummy).toEqual([1, 2, 3])
   })
+
+  // #12681
+  test('onScopeDispose inside non-immediate watcher', () => {
+    const cleanupSpy = vi.fn()
+    const cbSpy = vi.fn(() => {
+      onScopeDispose(cleanupSpy)
+    })
+    const scope = new EffectScope()
+
+    scope.run(() => {
+      const signal = ref(false)
+      watch(signal, cbSpy)
+      signal.value = true
+    })
+
+    scope.stop()
+
+    expect(cbSpy).toBeCalledTimes(1)
+    expect(cleanupSpy).toBeCalledTimes(1)
+  })
 })
diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts
index 39032a63699..f5e2b43930a 100644
--- a/packages/runtime-core/__tests__/apiWatch.spec.ts
+++ b/packages/runtime-core/__tests__/apiWatch.spec.ts
@@ -2010,4 +2010,24 @@ describe('api: watch', () => {
     createApp(App).mount(root)
     expect(onCleanup).toBeCalledTimes(0)
   })
+
+  // #12681
+  test('onScopeDispose inside non-immediate watcher that ran', () => {
+    const cleanupSpy = vi.fn()
+    const cbSpy = vi.fn(() => {
+      onScopeDispose(cleanupSpy)
+    })
+    const scope = effectScope()
+
+    scope.run(() => {
+      const signal = ref(false)
+      watch(signal, cbSpy)
+      signal.value = true
+    })
+
+    scope.stop()
+
+    expect(cbSpy).toBeCalledTimes(1)
+    expect(cleanupSpy).toBeCalledTimes(1)
+  })
 })