diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,21 @@
 # Revision history for cached-io
 
+## 1.4.0.0
+
+- **Breaking** All `cached...` functions now require `MonadMask` for the monad
+  running the refresh action. This was required to support the following bug
+  fix.
+- Fix a subtle bug: if you used a monad with explicit early-exit semantics like
+  `MaybeT m` or `ExceptT e m`, and your cached action uses this to exit early, the
+  cache would get stuck in `Initializing` state and subsequent requests for the
+  cached data would hang forever waiting for initialisation to complete.
+
 ## 1.3.2.0
 
 - Add `Functor` instance for `Cached`.
+- Add `cachedIOFromConfig` and `cachedSTMFromConfig` (and a supporting `Config`
+  type), which let the freshness check inspect the cached value in addition to
+  its timestamp.
 
 ## 1.3.1.0
 
diff --git a/cached-io.cabal b/cached-io.cabal
--- a/cached-io.cabal
+++ b/cached-io.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.2
 name:            cached-io
-version:         1.3.2.0
+version:         1.4.0.0
 synopsis:        A simple library to cache IO actions
 description:
   Provides functions that convert an IO action into a cached one by storing the
@@ -68,5 +68,6 @@
     ghc-options: -Werror
 
   build-depends:
-    , tasty        ^>=1.5
-    , tasty-hunit  ^>=0.10.0.3
+    , tasty         ^>=1.5
+    , tasty-hunit   ^>=0.10.0.3
+    , transformers  >=0.5.6     && <0.7
diff --git a/src/Control/Concurrent/CachedIO.hs b/src/Control/Concurrent/CachedIO.hs
--- a/src/Control/Concurrent/CachedIO.hs
+++ b/src/Control/Concurrent/CachedIO.hs
@@ -48,7 +48,7 @@
     writeTVar,
   )
 import Control.Monad (join)
-import Control.Monad.Catch (MonadCatch, onException)
+import Control.Monad.Catch (MonadMask, onError)
 import Control.Monad.IO.Class (MonadIO, liftIO)
 import Data.Time.Clock (NominalDiffTime, UTCTime, addUTCTime, getCurrentTime)
 
@@ -79,7 +79,7 @@
 -- either get the cached value or refresh, if the cache is older than 'interval'
 -- seconds.
 cachedIO ::
-  (MonadIO m, MonadIO t, MonadCatch t) =>
+  (MonadIO m, MonadIO t, MonadMask t) =>
   -- | Number of seconds before refreshing cache
   NominalDiffTime ->
   -- | IO action to cache
@@ -94,7 +94,7 @@
 -- either get the cached value or refresh, if the cache is older than 'interval'
 -- seconds.
 cachedIO' ::
-  (MonadIO m, MonadIO t, MonadCatch t) =>
+  (MonadIO m, MonadIO t, MonadMask t) =>
   -- | Number of seconds before refreshing cache
   NominalDiffTime ->
   -- | action to cache. The stale value and its refresh date
@@ -108,7 +108,7 @@
 -- The outer IO is responsible for setting up the cache. Use the inner one to
 -- either get the cached value or refresh
 cachedIOWith ::
-  (MonadIO m, MonadIO t, MonadCatch t) =>
+  (MonadIO m, MonadIO t, MonadMask t) =>
   -- | Test function:
   -- If @isCacheStillFresh lastUpdated now@ returns 'True',
   -- the cache is considered still fresh and returns the cached IO action
@@ -123,7 +123,7 @@
 -- The outer IO is responsible for setting up the cache. Use the inner one to
 -- either get the cached value or refresh
 cachedIOWith' ::
-  (MonadIO m, MonadIO t, MonadCatch t) =>
+  (MonadIO m, MonadIO t, MonadMask t) =>
   -- | Test function:
   -- If @isCacheStillFresh lastUpdated now@ returns 'True'
   -- the cache is considered still fresh and returns the cached IO action
@@ -143,7 +143,7 @@
 --
 -- The outer IO is responsible for setting up the cache. Use the inner one to
 -- either get the cached value or refresh
-cachedIOFromConfig :: (MonadIO m, MonadIO t, MonadCatch t) => Config t a -> m (Cached t a)
+cachedIOFromConfig :: (MonadIO m, MonadIO t, MonadMask t) => Config t a -> m (Cached t a)
 cachedIOFromConfig = liftIO . atomically . cachedSTMFromConfig
 
 -- $stm
@@ -154,7 +154,7 @@
 -- | Set up a cached IO action in a transaction; producing a version of this IO
 -- action that is cached for 'interval' seconds. The cache begins uninitialized.
 cachedSTM ::
-  (MonadIO m, MonadCatch m) =>
+  (MonadIO m, MonadMask m) =>
   -- | Number of seconds before refreshing cache
   NominalDiffTime ->
   -- | IO action to cache
@@ -165,7 +165,7 @@
 -- | Set up a cached IO action in a transaction; producing a version of this IO
 -- action that is cached for 'interval' seconds. The cache begins uninitialized.
 cachedSTM' ::
-  (MonadIO m, MonadCatch m) =>
+  (MonadIO m, MonadMask m) =>
   -- | Number of seconds before refreshing cache
   NominalDiffTime ->
   -- | action to cache. The stale value and its refresh date
@@ -176,7 +176,7 @@
 
 -- | Set up a cached IO action in a transaction; The cache begins uninitialized.
 cachedSTMWith ::
-  (MonadIO m, MonadCatch m) =>
+  (MonadIO m, MonadMask m) =>
   -- | Test function:
   -- If @isCacheStillFresh lastUpdated now@ returns 'True',
   -- the cache is considered still fresh and returns the cached IO action
@@ -188,7 +188,7 @@
 
 -- | Set up a cached IO action in a transaction; the cache begins uninitialized.
 cachedSTMWith' ::
-  (MonadIO m, MonadCatch m) =>
+  (MonadIO m, MonadMask m) =>
   -- | Test function:
   -- If @isCacheStillFresh lastUpdated now@ returns 'True'
   -- the cache is considered still fresh and returns the cached IO action
@@ -206,7 +206,7 @@
 
 -- | Set up a cached IO action in a transaction; the cache begins uninitialized.
 cachedSTMFromConfig ::
-  (MonadIO m, MonadCatch m) =>
+  (MonadIO m, MonadMask m) =>
   Config m a ->
   STM (Cached m a)
 cachedSTMFromConfig config = do
@@ -242,7 +242,7 @@
             _ -> Nothing
       newValue <-
         refreshAction config previous
-          `onException` liftIO (atomically (writeTVar cachedT previousState))
+          `onError` liftIO (atomically (writeTVar cachedT previousState))
       liftIO $ do
         now <- getCurrentTime
         atomically (writeTVar cachedT (Fresh now newValue))
diff --git a/test/test-cachedIO.hs b/test/test-cachedIO.hs
--- a/test/test-cachedIO.hs
+++ b/test/test-cachedIO.hs
@@ -2,6 +2,7 @@
 
 import Control.Concurrent (forkIO, threadDelay)
 import Control.Concurrent.CachedIO (Cached (..), cachedIO)
+import Control.Monad.Trans.Except (ExceptT (..), runExceptT, throwE)
 import Data.Functor (void)
 import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef)
 import Test.Tasty (TestTree, defaultMain, testGroup)
@@ -29,7 +30,15 @@
         void . forkIO $ void action
         void action
         count <- readIORef ref
-        count @?= 1
+        count @?= 1,
+      testCase "Cache resets when ExceptT action fails via throwError" $ do
+        Cached action <- cachedIO (5 * 60) (throwE () :: ExceptT () IO ())
+        result1 <- runExceptT action
+        result1 @?= Left ()
+        -- If we do not handle the throwE correctly this will hang because the
+        -- cache is waiting to finish initialising.
+        result2 <- runExceptT action
+        result2 @?= Left ()
     ]
 
 increment :: IORef Int -> IO Int
