diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [0.9.4] - 2022-05-17
+## [0.9.5] - 2022-05-17
 
 ### Added
 
@@ -24,6 +24,10 @@
 ### Removed
 
  - `hercules-ci-agent-nix_2_5` variant: upgrade to plain `hercules-ci-agent` (2.8.0) or `_nix_2_7`.
+
+## 0.9.4 - 2022-05-17
+
+Bad release. See 0.9.5 for changes.
 
 ## [0.9.3] - 2022-04-08
 
diff --git a/hercules-ci-agent-worker/Hercules/Agent/Worker.hs b/hercules-ci-agent-worker/Hercules/Agent/Worker.hs
--- a/hercules-ci-agent-worker/Hercules/Agent/Worker.hs
+++ b/hercules-ci-agent-worker/Hercules/Agent/Worker.hs
@@ -302,7 +302,7 @@
 dropMiddle :: MonadIO m => ConduitM (Flush LogEntry) (Flush LogEntry) m ()
 dropMiddle = do
   -- rich logging
-  takeCWhileStopEarly isChunk richLogLimit
+  _ <- takeCWhileStopEarly isChunk richLogLimit
   -- degrade to text logging (in case rich logging produces excessive non-textual data)
   visibleLinesOnly .| withMessageLimit isChunk textOnlyLogLimit tailLimit snipStart snip snipped
 
diff --git a/hercules-ci-agent.cabal b/hercules-ci-agent.cabal
--- a/hercules-ci-agent.cabal
+++ b/hercules-ci-agent.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.4
 
 name:           hercules-ci-agent
-version:        0.9.4
+version:        0.9.5
 synopsis:       Runs Continuous Integration tasks on your machines
 category:       Nix, CI, Testing, DevOps
 homepage:       https://docs.hercules-ci.com
diff --git a/src/Hercules/Agent/Socket.hs b/src/Hercules/Agent/Socket.hs
--- a/src/Hercules/Agent/Socket.hs
+++ b/src/Hercules/Agent/Socket.hs
@@ -33,7 +33,8 @@
 import Network.WebSockets (Connection, runClientWith)
 import qualified Network.WebSockets as WS
 import Protolude hiding (atomically, handle, race, race_)
-import UnliftIO.Async (AsyncCancelled (AsyncCancelled), race, race_)
+import qualified UnliftIO
+import UnliftIO.Async (race, race_)
 import UnliftIO.Exception (handle)
 import UnliftIO.STM (readTVarIO)
 import UnliftIO.Timeout (timeout)
@@ -225,13 +226,13 @@
             pass
           else noAckCleanupThread' expectedN
   forever do
-    removeTimeout <- prepareTimeout handshakeTimeoutMicroseconds HandshakeTimeout
     handle logWarningPause $
-      withConnection' socketConfig $
-        \conn -> do
-          katipAddNamespace "Handshake" do
-            handshake conn removeTimeout
-          readThread conn `race_` writeThread conn `race_` noAckCleanupThread
+      withCancelableTimeout handshakeTimeoutMicroseconds HandshakeTimeout \removeTimeout -> do
+        withConnection' socketConfig $
+          \conn -> do
+            katipAddNamespace "Handshake" do
+              handshake conn removeTimeout
+            readThread conn `race_` writeThread conn `race_` noAckCleanupThread
 
 handshakeTimeoutMicroseconds :: Int
 handshakeTimeoutMicroseconds = 30_000_000
@@ -239,17 +240,15 @@
 data HandshakeTimeout = HandshakeTimeout
   deriving (Show, Exception)
 
-prepareTimeout :: (Exception e, MonadIO m) => Int -> e -> m (IO ())
-prepareTimeout delay exc = do
+withCancelableTimeout :: (Exception e, MonadUnliftIO m) => Int -> e -> (IO () -> m a) -> m a
+withCancelableTimeout delay exc cont = do
   requestingThread <- liftIO myThreadId
-  tid <- liftIO $ forkIO do
-    do
-      threadDelay delay
-      throwTo requestingThread exc
-      `catch` \(_ :: AsyncCancelled) ->
-        -- Removal of the timeout is normal, so do nothing
-        pass
-  pure $ throwTo tid AsyncCancelled
+  UnliftIO.withAsync
+    ( liftIO do
+        threadDelay delay
+        throwTo requestingThread exc
+    )
+    (cont . cancel)
 
 msgN :: Frame o a -> Maybe Integer
 msgN Frame.Msg {n = n} = Just n
