diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+# v0.1.2.2
+
+## Fixes
+
+- Fixed the reference adapter's `ntuples` and `nfields` intermittently returning garbage, which made the differential suite fail correct candidates. `postgresql-libpq` imports `PQntuples` and `PQnfields` as pure functions and lets the resulting thunk escape `withForeignPtr`, so if the result handle got collected before the count was forced, the number was read from memory that `PQclear` had already freed. Both counts are now forced while the handle is still alive.
+
 # v0.1.2.1
 
 - Fixed the publishing.
diff --git a/pqi-conformance.cabal b/pqi-conformance.cabal
--- a/pqi-conformance.cabal
+++ b/pqi-conformance.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: pqi-conformance
-version: 0.1.2.1
+version: 0.1.2.2
 category: Database, PostgreSQL, Testing
 synopsis: Differential conformance tests for pqi adapters
 description:
diff --git a/src/library/Pqi/Conformance/Reference.hs b/src/library/Pqi/Conformance/Reference.hs
--- a/src/library/Pqi/Conformance/Reference.hs
+++ b/src/library/Pqi/Conformance/Reference.hs
@@ -9,6 +9,8 @@
 where
 
 import qualified Database.PostgreSQL.LibPQ as LibPQ
+import qualified GHC.Exts
+import qualified GHC.IO
 import qualified Pqi
 import Pqi.Conformance.Prelude
 
@@ -119,8 +121,8 @@
       Pqi.resultErrorMessage = LibPQ.resultErrorMessage r,
       Pqi.resultErrorField = \field -> LibPQ.resultErrorField r (toFieldCode field),
       Pqi.unsafeFreeResult = LibPQ.unsafeFreeResult r,
-      Pqi.ntuples = fromRow <$> LibPQ.ntuples r,
-      Pqi.nfields = fromColumn <$> LibPQ.nfields r,
+      Pqi.ntuples = strictly r (fromRow <$> LibPQ.ntuples r),
+      Pqi.nfields = strictly r (fromColumn <$> LibPQ.nfields r),
       Pqi.fname = \column -> LibPQ.fname r (toColumn column),
       Pqi.fnumber = \name -> fmap fromColumn <$> LibPQ.fnumber r name,
       Pqi.ftable = \column -> fromOid <$> LibPQ.ftable r (toColumn column),
@@ -138,6 +140,25 @@
       Pqi.cmdStatus = LibPQ.cmdStatus r,
       Pqi.cmdTuples = LibPQ.cmdTuples r
     }
+
+-- | Read a number out of a result handle, forcing it while the handle is still
+-- guaranteed to be alive.
+--
+-- @postgresql-libpq@ imports @PQntuples@ and @PQnfields@ as /pure/ functions
+-- and defines the wrappers as @withResult res (return . toRow . c_PQntuples)@,
+-- so the C call escapes 'Foreign.ForeignPtr.withForeignPtr' as an unevaluated
+-- thunk over the raw @PGresult@ pointer. Should the handle become unreachable
+-- before that thunk is forced, its @PQclear@ finalizer has already freed the
+-- memory and the number read is garbage — which made the reference disagree
+-- with a correct candidate whenever a collection happened to land in the
+-- window. Every other accessor is imported in 'IO' and is unaffected.
+strictly :: LibPQ.Result -> IO Int32 -> IO Int32
+strictly r io = do
+  !n <- io
+  touch r
+  pure n
+  where
+    touch x = GHC.IO.IO \s -> case GHC.Exts.touch# x s of s' -> (# s', () #)
 
 -- | Build a 'Pqi.Cancel' whose field closes over the given
 -- @postgresql-libpq@ cancellation handle.
