diff --git a/rewrite.cabal b/rewrite.cabal
--- a/rewrite.cabal
+++ b/rewrite.cabal
@@ -1,5 +1,5 @@
 name:                rewrite
-version:             0.6
+version:             0.8
 synopsis:            open file and rewrite it with new contents
 description:
   In the Unix shell there is no easy way to use a filter program
@@ -33,6 +33,7 @@
       base ==4.6.*
     , multiarg ==0.24.*
     , directory ==1.2.*
-    , process-extras ==0.2.*
     , bytestring ==0.10.*
-  ghc-options: -Wall
+    , process ==1.1.*
+
+  ghc-options: -Wall -threaded
diff --git a/rewrite.hs b/rewrite.hs
--- a/rewrite.hs
+++ b/rewrite.hs
@@ -4,9 +4,13 @@
 import qualified System.Console.MultiArg as MA
 import qualified System.IO as IO
 import qualified System.Exit as Exit
-import System.Process.ByteString (readProcessWithExitCode)
 import qualified Data.ByteString as BS
 import qualified System.Directory as D
+import qualified Control.Concurrent as C
+import qualified Control.Exception as Ex
+import qualified System.Process as P
+import qualified Paths_rewrite as Pr
+import qualified Data.Version as Ver
 
 help :: String -> String
 help pn = unlines
@@ -17,11 +21,17 @@
   , "Aborts if PROGRAM exits with non-zero exit status."
   , ""
   , "Options:"
-  , "  -h, --help          Show help and exit"
   , "  -b, --backup SUFFIX Back up FILE to file with given SUFFIX"
   , "                      before writing new file"
+  , ""
+  , "  -h, --help          Show help and exit"
+  , "  --version           Show version and exit"
   ]
 
+version :: String -> String
+version pn = unlines
+  [ pn ++ " " ++ Ver.showVersion Pr.version ]
+
 type Backup = String
 type ProgramOpt = String
 type ProgName = String
@@ -38,7 +48,8 @@
 parseArgs :: IO (Maybe Backup, InputFile, ProgName, [ProgramOpt])
 parseArgs = do
   let opts = [ MA.OptSpec ["backup"] "b" (MA.OneArg (return . Left)) ]
-  as <- MA.simpleHelp help opts MA.StopOptions (return . Right)
+  as <- MA.simpleHelpVersion help version
+                             opts MA.StopOptions (return . Right)
   let (baks, os) = partitionEithers as
   bak <- case baks of
     [] -> return Nothing
@@ -70,8 +81,7 @@
   -> IO ()
 runProgram mayBak inFile pn opts = do
   input <- BS.readFile inFile
-  (code, out, err) <- readProcessWithExitCode pn opts input
-  BS.hPutStr IO.stderr err
+  (code, out) <- readProcess pn opts input
   _ <- case code of
     Exit.ExitSuccess -> return ()
     Exit.ExitFailure bad ->
@@ -83,7 +93,100 @@
   BS.writeFile inFile out
 
 
+-- | Given the name of a program, the options, and the data to feed to
+-- the program's standard input, runs the program in a subprocess.
+-- Returns the exit code from the program and the text printed to the
+-- program's standard output.  Any output the subprocess prints to its
+-- standard error is printed to rewrite's standard error.
+--
+-- Previous versions of rewrite simply used 'readProcessWithExitCode'
+-- from "System.Process.ByteString" from the process-extras package.
+-- However, this would result in bad behavior if the subprocess
+-- failed.  For example, if you ran
+--
+-- > penny-fit -f amex clear
+--
+-- then you would get
+--
+-- > penny-fit: user error (clear: you must provide a postings file.)
+--
+-- and the program would exit with an error.  However, if you ran
+--
+-- > rewrite main.pny penny-fit -f amex clear
+--
+-- you would get
+--
+-- > rewrite: fd:5: hPutBuf: resource vanished (Broken pipe)
+--
+-- which is not a helpful error message.  This function ensures that
+-- the standard error from the subprocess is always printed, which
+-- results in much more useful error messages.  Now the same command
+-- yields:
+--
+-- > penny-fit: user error (clear: you must provide a postings file.)
+-- > rewrite: fd:9: hPutBuf: resource vanished (Broken pipe)
+--
+-- which still has some gobbledygook about hPutBuf, but at least now
+-- there is also the underlying program's error message.  (The second
+-- line could be stripped out, but I don't see the point in this and
+-- sometimes such errors might be helpful.)
+--
+-- All intermediate results are buffered in memory, so rewrite may not
+-- be suitable for dealing with huge intermediate results that are
+-- hard to buffer in available memory.
+
+readProcess
+  :: ProgName
+  -> [ProgramOpt]
+  -> BS.ByteString
+  -> IO (Exit.ExitCode, BS.ByteString)
+readProcess pn os bs = do
+  let cp = P.CreateProcess
+        { P.cmdspec = P.RawCommand pn os
+        , P.cwd = Nothing
+        , P.env = Nothing
+        , P.std_in = P.CreatePipe
+        , P.std_out = P.CreatePipe
+        , P.std_err = P.CreatePipe
+        , P.close_fds = False
+        , P.create_group = False }
+  (Just i, Just o, Just e, h) <- P.createProcess cp
+  mWriteBS <- forkThreadMVar (BS.hPut i bs)
+  mReadOut <- forkThreadMVar (BS.hGetContents o)
+  mReadErr <- forkThreadMVar (BS.hGetContents e)
+  mCode <- forkThreadMVar (P.waitForProcess h)
+  strErr <- throwErr mReadErr
+  BS.hPutStr IO.stderr strErr
+  c <- throwErr mCode
+  _ <- throwErr mWriteBS
+  out <- throwErr mReadOut
+  return (c, out)
+
+
+-- | Fetches a value stored in an MVar. Throws if the value is an
+-- exception; otherwise, returns the value.
+throwErr :: C.MVar (Either Ex.SomeException a) -> IO a
+throwErr m = do
+  m' <- C.readMVar m
+  case m' of
+    Left e -> Ex.throwIO e
+    Right g -> return g
+
+
+-- | Forks a new thread, and supplies the result of the computation
+-- (or any resulting exception) in an MVar. To wait until the thread
+-- exits, get the MVar's value.
+forkThreadMVar
+  :: IO a
+  -> IO (C.MVar (Either Ex.SomeException a))
+forkThreadMVar i = do
+  m <- C.newEmptyMVar
+  _ <- C.forkFinally i $ \e -> C.putMVar m e
+  return m
+
+
 main :: IO ()
 main = do
   (mayBak, inf, pn, opts) <- parseArgs
   runProgram mayBak inf pn opts
+
