diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,9 @@
 # ChangeLog for typed-process
 
+## 0.2.10.0
+
+* Add `mkPipeStreamSpec`
+
 ## 0.2.9.0
 
 * Re-export `StdStream`
diff --git a/src/System/Process/Typed.hs b/src/System/Process/Typed.hs
--- a/src/System/Process/Typed.hs
+++ b/src/System/Process/Typed.hs
@@ -71,6 +71,7 @@
 
     -- ** Create your own stream spec
     , mkStreamSpec
+    , mkPipeStreamSpec
 
       -- | #launchaprocess#
 
@@ -580,6 +581,9 @@
 -- * Returns the actual stream value @a@, as well as a cleanup
 -- function to be run when calling 'stopProcess'.
 --
+-- If making a 'StreamSpec' with 'P.CreatePipe', prefer 'mkPipeStreamSpec',
+-- which encodes the invariant that a 'Handle' is created.
+--
 -- @since 0.1.0.0
 mkStreamSpec :: P.StdStream
              -- ^
@@ -588,6 +592,23 @@
              -> StreamSpec streamType a
 mkStreamSpec ss f = mkManagedStreamSpec ($ ss) f
 
+-- | Create a new 'P.CreatePipe' 'StreamSpec' from the given function.
+-- This function:
+--
+-- * Takes as input the @Handle@ returned by the 'P.createProcess' function.
+-- See 'P.createProcess' for more details.
+--
+-- * Returns the actual stream value @a@, as well as a cleanup
+-- function to be run when calling 'stopProcess'.
+--
+-- @since 0.2.10.0
+mkPipeStreamSpec :: (ProcessConfig () () () -> Handle -> IO (a, IO ()))
+                 -> StreamSpec streamType a
+mkPipeStreamSpec f = mkStreamSpec P.CreatePipe $ \pc mh ->
+    case mh of
+        Just h -> f pc h
+        Nothing -> error "Invariant violation: making StreamSpec with CreatePipe unexpectedly did not return a Handle"
+
 -- | Create a new 'StreamSpec' from a function that accepts a
 -- 'P.StdStream' and a helper function.  This function is the same as
 -- the helper in 'mkStreamSpec'
@@ -603,7 +624,7 @@
 --
 -- @since 0.1.0.0
 inherit :: StreamSpec anyStreamType ()
-inherit = mkStreamSpec P.Inherit (\_ Nothing -> pure ((), return ()))
+inherit = mkStreamSpec P.Inherit (\_ _ -> pure ((), return ()))
 
 -- | A stream spec which is empty when used for for input and discards
 -- output.  Note this requires your platform's null device to be
@@ -628,9 +649,9 @@
 -- @since 0.1.0.0
 closed :: StreamSpec anyStreamType ()
 #if MIN_VERSION_process(1, 4, 0)
-closed = mkStreamSpec P.NoStream (\_ Nothing -> pure ((), return ()))
+closed = mkStreamSpec P.NoStream (\_ _ -> pure ((), return ()))
 #else
-closed = mkStreamSpec P.CreatePipe (\_ (Just h) -> ((), return ()) <$ hClose h)
+closed = mkPipeStreamSpec (\_ h -> ((), return ()) <$ hClose h)
 #endif
 
 -- | An input stream spec which sets the input to the given
@@ -639,7 +660,7 @@
 --
 -- @since 0.1.0.0
 byteStringInput :: L.ByteString -> StreamSpec 'STInput ()
-byteStringInput lbs = mkStreamSpec P.CreatePipe $ \_ (Just h) -> do
+byteStringInput lbs = mkPipeStreamSpec $ \_ h -> do
     void $ async $ do
         L.hPut h lbs
         hClose h
@@ -659,7 +680,7 @@
 --
 -- @since 0.1.0.0
 byteStringOutput :: StreamSpec 'STOutput (STM L.ByteString)
-byteStringOutput = mkStreamSpec P.CreatePipe $ \pc (Just h) -> byteStringFromHandle pc h
+byteStringOutput = mkPipeStreamSpec $ \pc h -> byteStringFromHandle pc h
 
 -- | Helper function (not exposed) for both 'byteStringOutput' and
 -- 'withProcessInterleave'. This will consume all of the output from
@@ -690,7 +711,7 @@
 --
 -- @since 0.1.0.0
 createPipe :: StreamSpec anyStreamType Handle
-createPipe = mkStreamSpec P.CreatePipe $ \_ (Just h) -> return (h, hClose h)
+createPipe = mkPipeStreamSpec $ \_ h -> return (h, hClose h)
 
 -- | Use the provided 'Handle' for the child process, and when the
 -- process exits, do /not/ close it. This is useful if, for example,
@@ -699,7 +720,7 @@
 --
 -- @since 0.1.0.0
 useHandleOpen :: Handle -> StreamSpec anyStreamType ()
-useHandleOpen h = mkStreamSpec (P.UseHandle h) $ \_ Nothing -> return ((), return ())
+useHandleOpen h = mkStreamSpec (P.UseHandle h) $ \_ _ -> return ((), return ())
 
 -- | Use the provided 'Handle' for the child process, and when the
 -- process exits, close it. If you have no reason to keep the 'Handle'
@@ -707,7 +728,7 @@
 --
 -- @since 0.1.0.0
 useHandleClose :: Handle -> StreamSpec anyStreamType ()
-useHandleClose h = mkStreamSpec (P.UseHandle h) $ \_ Nothing -> return ((), hClose h)
+useHandleClose h = mkStreamSpec (P.UseHandle h) $ \_ _ -> return ((), hClose h)
 
 -- | Launch a process based on the given 'ProcessConfig'. You should
 -- ensure that you call 'stopProcess' on the result. It's usually
@@ -1059,7 +1080,7 @@
         -- Use the writer end of the pipe for both stdout and stderr. For
         -- the stdout half, use byteStringFromHandle to read the data into
         -- a lazy ByteString in memory.
-        let pc' = setStdout (mkStreamSpec (P.UseHandle writeEnd) (\pc'' Nothing -> byteStringFromHandle pc'' readEnd))
+        let pc' = setStdout (mkStreamSpec (P.UseHandle writeEnd) (\pc'' _ -> byteStringFromHandle pc'' readEnd))
                 $ setStderr (useHandleOpen writeEnd)
                   pc
         withProcess pc' $ \p -> do
diff --git a/typed-process.cabal b/typed-process.cabal
--- a/typed-process.cabal
+++ b/typed-process.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           typed-process
-version:        0.2.9.0
+version:        0.2.10.0
 synopsis:       Run external processes, with strong typing of streams
 description:    Please see the tutorial at <https://github.com/fpco/typed-process#readme>
 category:       System
