diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# effectful-core-2.2.2.1 (2023-01-12)
+* Stop using the internal library because of bugs in `stack`.
+
 # effectful-core-2.2.2.0 (2023-01-11)
 * Add `withSeqEffToIO` and `withConcEffToIO` to `Effectful`.
 * Use strict `IORef` and `MVar` variants where appropriate.
diff --git a/cbits/utils.c b/cbits/utils.c
new file mode 100644
--- /dev/null
+++ b/cbits/utils.c
@@ -0,0 +1,5 @@
+// Correct implementation of ThreadId# equality for GHC < 9.
+long effectful_eq_thread(void *tso1, void *tso2)
+{
+  return tso1 == tso2;
+}
diff --git a/effectful-core.cabal b/effectful-core.cabal
--- a/effectful-core.cabal
+++ b/effectful-core.cabal
@@ -1,7 +1,7 @@
 cabal-version:      2.4
 build-type:         Simple
 name:               effectful-core
-version:            2.2.2.0
+version:            2.2.2.1
 license:            BSD-3-Clause
 license-file:       LICENSE
 category:           Control
@@ -54,23 +54,11 @@
                         TypeFamilies
                         TypeOperators
 
-library effectful-internal-utils
-    import:          language
-
-    build-depends:   base
-
-    hs-source-dirs:  utils
-    c-sources:       utils/utils.c
-
-    exposed-modules: Effectful.Internal.Utils
-
 library
     import:         language
 
     ghc-options:    -O2
 
-    build-depends:  effectful-internal-utils
-
     build-depends:    base                >= 4.13      && < 5
                     , containers          >= 0.6
                     , exceptions          >= 0.10.4
@@ -81,6 +69,8 @@
 
     hs-source-dirs:  src
 
+    c-sources:       cbits/utils.c
+
     exposed-modules: Effectful
                      Effectful.Dispatch.Dynamic
                      Effectful.Dispatch.Static
@@ -93,6 +83,7 @@
                      Effectful.Internal.Env
                      Effectful.Internal.Monad
                      Effectful.Internal.Unlift
+                     Effectful.Internal.Utils
                      Effectful.NonDet
                      Effectful.Prim
                      Effectful.Reader.Dynamic
@@ -103,5 +94,3 @@
                      Effectful.Writer.Dynamic
                      Effectful.Writer.Static.Local
                      Effectful.Writer.Static.Shared
-
-    reexported-modules: Effectful.Internal.Utils
diff --git a/src/Effectful/Internal/Utils.hs b/src/Effectful/Internal/Utils.hs
new file mode 100644
--- /dev/null
+++ b/src/Effectful/Internal/Utils.hs
@@ -0,0 +1,120 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE UnliftedFFITypes #-}
+{-# OPTIONS_HADDOCK not-home #-}
+module Effectful.Internal.Utils
+  ( weakThreadId
+  , eqThreadId
+
+  -- * Strict 'IORef'
+  , IORef'
+  , newIORef'
+  , readIORef'
+  , writeIORef'
+
+  -- * Strict 'MVar'
+  , MVar'
+  , toMVar'
+  , newMVar'
+  , readMVar'
+  , modifyMVar'
+  , modifyMVar_'
+
+  -- * Utils for 'Any'
+  , Any
+  , toAny
+  , fromAny
+  ) where
+
+import Control.Concurrent.MVar
+import Data.IORef
+import Foreign.C.Types
+import GHC.Conc.Sync (ThreadId(..))
+import GHC.Exts (Addr#, Any, ThreadId#, unsafeCoerce#)
+import Unsafe.Coerce (unsafeCoerce)
+
+-- | Get an id of a thread that doesn't prevent its garbage collection.
+weakThreadId :: ThreadId -> Int
+weakThreadId (ThreadId t#) = fromIntegral $ rts_getThreadId (threadIdToAddr# t#)
+
+foreign import ccall unsafe "rts_getThreadId"
+#if __GLASGOW_HASKELL__ >= 904
+  -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6163
+  rts_getThreadId :: Addr# -> CULLong
+#elif __GLASGOW_HASKELL__ >= 900
+  -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/1254
+  rts_getThreadId :: Addr# -> CLong
+#else
+  rts_getThreadId :: Addr# -> CInt
+#endif
+
+-- | 'Eq' instance for 'ThreadId' is broken in GHC < 9, see
+-- https://gitlab.haskell.org/ghc/ghc/-/issues/16761 for more info.
+eqThreadId :: ThreadId -> ThreadId -> Bool
+eqThreadId (ThreadId t1#) (ThreadId t2#) =
+  eq_thread (threadIdToAddr# t1#) (threadIdToAddr# t2#) == 1
+
+foreign import ccall unsafe "effectful_eq_thread"
+  eq_thread :: Addr# -> Addr# -> CLong
+
+-- Note: FFI imports take Addr# instead of ThreadId# because of
+-- https://gitlab.haskell.org/ghc/ghc/-/issues/8281, which would prevent loading
+-- effectful-core into GHCi.
+--
+-- Previous workaround was to use an internal library with just this module, but
+-- this is not viable because of bugs in stack (sigh).
+--
+-- The coercion is fine because GHC represents ThreadId# as a pointer.
+threadIdToAddr# :: ThreadId# -> Addr#
+threadIdToAddr# = unsafeCoerce#
+
+----------------------------------------
+
+-- | A strict variant of 'IORef'.
+newtype IORef' a = IORef' (IORef a)
+
+newIORef' :: a -> IO (IORef' a)
+newIORef' a = a `seq` (IORef' <$> newIORef a)
+
+readIORef' :: IORef' a -> IO a
+readIORef' (IORef' var) = readIORef var
+
+writeIORef' :: IORef' a -> a -> IO ()
+writeIORef' (IORef' var) a = a `seq` writeIORef var a
+
+----------------------------------------
+
+-- | A strict variant of 'MVar'.
+newtype MVar' a = MVar' (MVar a)
+
+toMVar' :: MVar a -> IO (MVar' a)
+toMVar' var = do
+  let var' = MVar' var
+  modifyMVar_' var' pure
+  pure var'
+
+newMVar' :: a -> IO (MVar' a)
+newMVar' a = a `seq` (MVar' <$> newMVar a)
+
+readMVar' :: MVar' a -> IO a
+readMVar' (MVar' var) = readMVar var
+
+modifyMVar' :: MVar' a -> (a -> IO (a, r)) -> IO r
+modifyMVar' (MVar' var) action = modifyMVar var $ \a0 -> do
+  (a, r) <- action a0
+  a `seq` pure (a, r)
+{-# INLINE modifyMVar' #-}
+
+modifyMVar_' :: MVar' a -> (a -> IO a) -> IO ()
+modifyMVar_' (MVar' var) action = modifyMVar_ var $ \a0 -> do
+  a <- action a0
+  a `seq` pure a
+{-# INLINE modifyMVar_' #-}
+
+----------------------------------------
+
+toAny :: a -> Any
+toAny = unsafeCoerce
+
+fromAny :: Any -> a
+fromAny = unsafeCoerce
diff --git a/utils/Effectful/Internal/Utils.hs b/utils/Effectful/Internal/Utils.hs
deleted file mode 100644
--- a/utils/Effectful/Internal/Utils.hs
+++ /dev/null
@@ -1,108 +0,0 @@
-{-# LANGUAGE CPP #-}
-{-# LANGUAGE MagicHash #-}
-{-# LANGUAGE UnliftedFFITypes #-}
-{-# OPTIONS_HADDOCK not-home #-}
-module Effectful.Internal.Utils
-  ( weakThreadId
-  , eqThreadId
-
-  -- * Strict 'IORef'
-  , IORef'
-  , newIORef'
-  , readIORef'
-  , writeIORef'
-
-  -- * Strict 'MVar'
-  , MVar'
-  , toMVar'
-  , newMVar'
-  , readMVar'
-  , modifyMVar'
-  , modifyMVar_'
-
-  -- * Utils for 'Any'
-  , Any
-  , toAny
-  , fromAny
-  ) where
-
-import Control.Concurrent.MVar
-import Data.IORef
-import Foreign.C.Types
-import GHC.Conc.Sync (ThreadId(..))
-import GHC.Exts (Any, ThreadId#)
-import Unsafe.Coerce (unsafeCoerce)
-
--- | Get an id of a thread that doesn't prevent its garbage collection.
-weakThreadId :: ThreadId -> Int
-weakThreadId (ThreadId t#) = fromIntegral $ rts_getThreadId t#
-
-foreign import ccall unsafe "rts_getThreadId"
-#if __GLASGOW_HASKELL__ >= 904
-  -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/6163
-  rts_getThreadId :: ThreadId# -> CULLong
-#elif __GLASGOW_HASKELL__ >= 900
-  -- https://gitlab.haskell.org/ghc/ghc/-/merge_requests/1254
-  rts_getThreadId :: ThreadId# -> CLong
-#else
-  rts_getThreadId :: ThreadId# -> CInt
-#endif
-
--- | 'Eq' instance for 'ThreadId' is broken in GHC < 9, see
--- https://gitlab.haskell.org/ghc/ghc/-/issues/16761 for more info.
-eqThreadId :: ThreadId -> ThreadId -> Bool
-eqThreadId (ThreadId t1#) (ThreadId t2#) = eq_thread t1# t2# == 1
-
-foreign import ccall unsafe "effectful_eq_thread"
-  eq_thread :: ThreadId# -> ThreadId# -> CLong
-
-----------------------------------------
-
--- | A strict variant of 'IORef'.
-newtype IORef' a = IORef' (IORef a)
-
-newIORef' :: a -> IO (IORef' a)
-newIORef' a = a `seq` (IORef' <$> newIORef a)
-
-readIORef' :: IORef' a -> IO a
-readIORef' (IORef' var) = readIORef var
-
-writeIORef' :: IORef' a -> a -> IO ()
-writeIORef' (IORef' var) a = a `seq` writeIORef var a
-
-----------------------------------------
-
--- | A strict variant of 'MVar'.
-newtype MVar' a = MVar' (MVar a)
-
-toMVar' :: MVar a -> IO (MVar' a)
-toMVar' var = do
-  let var' = MVar' var
-  modifyMVar_' var' pure
-  pure var'
-
-newMVar' :: a -> IO (MVar' a)
-newMVar' a = a `seq` (MVar' <$> newMVar a)
-
-readMVar' :: MVar' a -> IO a
-readMVar' (MVar' var) = readMVar var
-
-modifyMVar' :: MVar' a -> (a -> IO (a, r)) -> IO r
-modifyMVar' (MVar' var) action = modifyMVar var $ \a0 -> do
-  (a, r) <- action a0
-  a `seq` pure (a, r)
-{-# INLINE modifyMVar' #-}
-
-modifyMVar_' :: MVar' a -> (a -> IO a) -> IO ()
-modifyMVar_' (MVar' var) action = modifyMVar_ var $ \a0 -> do
-  a <- action a0
-  a `seq` pure a
-{-# INLINE modifyMVar_' #-}
-
-----------------------------------------
-
-toAny :: a -> Any
-toAny = unsafeCoerce
-
-fromAny :: Any -> a
-fromAny = unsafeCoerce
diff --git a/utils/utils.c b/utils/utils.c
deleted file mode 100644
--- a/utils/utils.c
+++ /dev/null
@@ -1,5 +0,0 @@
-// Correct implementation of ThreadId# equality for GHC < 9.
-long effectful_eq_thread(void *tso1, void *tso2)
-{
-  return tso1 == tso2;
-}
