diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,15 @@
 # Changelog
 
+## 0.9.0.1 — 2026-09-25
+
+### Bug Fixes
+
+* The publisher now forces its scalar global position before storing it when
+  no all-stream queue subscribers are registered (BUG-3). This prevents a
+  chain of unevaluated position updates from retaining earlier Hasql results
+  and large objects during sustained appends. A post-major-GC regression test
+  covers the idle publisher path.
+
 ## 0.9.0.0 — 2026-09-25
 
 ### Breaking Changes
diff --git a/kiroku-store.cabal b/kiroku-store.cabal
--- a/kiroku-store.cabal
+++ b/kiroku-store.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.0
 name:            kiroku-store
-version:         0.9.0.0
+version:         0.9.0.1
 synopsis:        High-performance PostgreSQL event store
 description:
   Kiroku is a PostgreSQL-backed event store for Haskell applications. It
@@ -139,7 +139,7 @@
     Test.VisibleGlobalHeadPosition
     Test.VisibleGlobalHeadPositionMock
 
-  ghc-options:    -threaded -rtsopts -with-rtsopts=-N
+  ghc-options:    -threaded -rtsopts "-with-rtsopts=-N -T"
   build-depends:
     , aeson                 >=2.1   && <2.3
     , async                 >=2.2   && <2.3
diff --git a/src/Kiroku/Store/Subscription/EventPublisher.hs b/src/Kiroku/Store/Subscription/EventPublisher.hs
--- a/src/Kiroku/Store/Subscription/EventPublisher.hs
+++ b/src/Kiroku/Store/Subscription/EventPublisher.hs
@@ -248,7 +248,10 @@
                     if IntMap.null subs'
                         then do
                             GlobalPosition cur <- readTVar posVar
-                            writeTVar posVar (GlobalPosition (max cur tailPos))
+                            let nextPos = max cur tailPos
+                            -- Do not store a chain of lazy max expressions that
+                            -- retains earlier tail-query results.
+                            nextPos `seq` writeTVar posVar (GlobalPosition nextPos)
                             pure False
                         else pure True
                 when raced fullFetch
diff --git a/test/Test/PublisherIdleAdvance.hs b/test/Test/PublisherIdleAdvance.hs
--- a/test/Test/PublisherIdleAdvance.hs
+++ b/test/Test/PublisherIdleAdvance.hs
@@ -8,16 +8,18 @@
 import Control.Concurrent.STM (readTVarIO)
 import Control.Exception (bracket)
 import Control.Lens ((&), (.~), (^.))
-import Control.Monad (when)
+import Control.Monad (forM, when)
 import Data.Aeson qualified as Aeson
 import Data.Generics.Labels ()
 import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
 import Data.IntMap.Strict qualified as IntMap
 import Data.List (sort)
 import Data.String (fromString)
+import GHC.Stats (GCDetails (..), RTSStats (..), getRTSStats, getRTSStatsEnabled)
 import Kiroku.Store
 import Kiroku.Store.Subscription.EventPublisher qualified as Pub
-import Test.Helpers (caughtUpEventHandler, makeEvent, waitForPublisher, waitForSubscriptionLive, withTestStoreSettings)
+import System.Mem (performMajorGC)
+import Test.Helpers (caughtUpEventHandler, makeEvent, waitForPublisher, waitForSubscriptionLive, withTestStore, withTestStoreSettings)
 import Test.Hspec
 
 timeoutMicros :: Int
@@ -62,8 +64,41 @@
 publisherSubscriberCount store =
     IntMap.size <$> readTVarIO (Pub.subscribers (store ^. #publisher))
 
+sampleLargeObjects :: IO Integer
+sampleLargeObjects = do
+    enabled <- getRTSStatsEnabled
+    when (not enabled) (fail "publisher retention test requires RTS statistics")
+    performMajorGC
+    fromIntegral . gcdetails_large_objects_bytes . gc <$> getRTSStats
+
 spec :: Spec
 spec = describe "publisher idle advance" $ do
+    it "does not retain append results while advancing with no queue subscribers" $ do
+        withTestStore $ \store -> do
+            let blockSize :: Int
+                blockSize = 2_000
+                blocks :: Int
+                blocks = 6
+                event = makeEvent "Retention" (Aeson.object [])
+                appendOne = do
+                    result <- runStoreIO store $ appendToStream (StreamName "pubidle-retention") AnyVersion [event]
+                    case result of
+                        Left err -> fail ("append failed: " <> show err)
+                        Right _ -> pure ()
+            samples <- forM [1 .. blocks] $ \_ -> do
+                sequence_ (replicate blockSize appendOne)
+                -- Allow the notification-driven publisher to finish its tail
+                -- query without reading (and thereby forcing) its position.
+                threadDelay 100_000
+                sampleLargeObjects
+            publisherSubscriberCount store `shouldReturn` 0
+            case samples of
+                firstSample : _ -> do
+                    let growth = last samples - firstSample
+                    when (growth >= 3 * 1024 * 1024) $
+                        expectationFailure ("large-object bytes grew by " <> show growth <> "; samples: " <> show samples)
+                [] -> expectationFailure "publisher retention test collected no heap samples"
+
     it "advances lastPublished without decoding any rows when no subscriber is registered" $ do
         counter <- newIORef 0
         withTestStoreSettings (countingSettings counter) $ \store -> do
