diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -2,6 +2,11 @@
 
 ## Unreleased
 
+## 0.2.1.3 - 2026-07-02
+- Require `status-notifier-item >= 0.3.2.15` so tray consumers pick up the
+  startup timeouts that prevent a single unresponsive tray item from hanging
+  the SNI host (and any tray built from it) indefinitely.
+
 ## 0.2.1.2 - 2026-05-13
 - Clean up compiler warnings in the standalone executable and tray modules.
 - Require refreshed companion packages:
diff --git a/gtk-sni-tray.cabal b/gtk-sni-tray.cabal
--- a/gtk-sni-tray.cabal
+++ b/gtk-sni-tray.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           gtk-sni-tray
-version:        0.2.1.2
+version:        0.2.1.3
 synopsis:       A standalone StatusNotifierItem/AppIndicator tray
 description:    Please see the README on Github at <https://github.com/taffybar/gtk-sni-tray#readme>
 category:       System
@@ -60,7 +60,7 @@
     , haskell-gi >=0.21.2 && <0.27
     , haskell-gi-base >=0.21.1 && <0.27
     , hslogger >=1.2 && <1.4
-    , status-notifier-item >=0.3.2.14 && <0.4
+    , status-notifier-item >=0.3.2.15 && <0.4
     , text >=1.2 && <2.2
     , transformers >=0.4 && <0.7
     , transformers-base ==0.4.*
diff --git a/src/StatusNotifier/Tray.hs b/src/StatusNotifier/Tray.hs
--- a/src/StatusNotifier/Tray.hs
+++ b/src/StatusNotifier/Tray.hs
@@ -212,13 +212,29 @@
   | width <= 0 || height <= 0 = do
       trayLogger WARNING $ printf "Invalid icon dimensions: %dx%d" width height
       return Nothing
+  | BS.length byteString < expectedLength = do
+      trayLogger WARNING $
+        printf
+          "Invalid icon payload length for dimensions %dx%d: got %d bytes, expected at least %d"
+          width
+          height
+          (BS.length byteString)
+          expectedLength
+      return Nothing
   | otherwise = catchGErrorsAsNothing $ do
       trayLogger DEBUG "Getting Pixbuf from bytestring"
       bytes <- bytesNew $ Just byteString
       let bytesPerPixel = 4
           rowStride = width * bytesPerPixel
           sampleBits = 8
-      pixbufNewFromBytes bytes ColorspaceRgb True sampleBits width height rowStride
+      pixbuf <- pixbufNewFromBytes bytes ColorspaceRgb True sampleBits width height rowStride
+      -- The pixbuf returned by pixbufNewFromBytes is backed by immutable GBytes.
+      -- Return a private copy so later overlay composition or caller transforms
+      -- never write into SNI-owned byte storage.
+      fromMaybe pixbuf <$> Gdk.pixbufCopy pixbuf
+  where
+    expectedLength =
+      fromIntegral width * fromIntegral height * 4
 
 data ItemContext = ItemContext
   { contextName :: DBusTypes.BusName,
@@ -913,6 +929,7 @@
           updateHandler _ _ = return ()
 
           maybeAddOverlayToPixbuf size info pixbuf = do
+            destination <- fromMaybe pixbuf <$> Gdk.pixbufCopy pixbuf
             _ <- runMaybeT $ do
               let overlayHeight = floor (fromIntegral size * overlayScale)
               overlayPixbuf <-
@@ -922,11 +939,11 @@
               lift $ do
                 actualOHeight <- getPixbufHeight overlayPixbuf
                 actualOWidth <- getPixbufWidth overlayPixbuf
-                _mainHeight <- getPixbufHeight pixbuf
-                _mainWidth <- getPixbufWidth pixbuf
+                _mainHeight <- getPixbufHeight destination
+                _mainWidth <- getPixbufWidth destination
                 pixbufComposite
                   overlayPixbuf
-                  pixbuf
+                  destination
                   0
                   0
                   actualOWidth
@@ -937,7 +954,7 @@
                   1.0
                   InterpTypeBilinear
                   255
-            return pixbuf
+            return destination
 
           getScaledPixBufFromInfo size info =
             getPixBufFromInfo size info
