diff --git a/Network/Wai/Handler/DevelServer.hs b/Network/Wai/Handler/DevelServer.hs
--- a/Network/Wai/Handler/DevelServer.hs
+++ b/Network/Wai/Handler/DevelServer.hs
@@ -17,9 +17,10 @@
 import Data.Text.Lazy (pack)
 import Data.Text.Lazy.Encoding (encodeUtf8)
 import qualified Data.ByteString.Lazy.Char8 as L8
-import Control.Exception (Exception, SomeException, toException, fromException)
+import Control.Exception (Exception, SomeException, toException, fromException, finally, mask)
 import qualified Control.Exception as E
-import Control.Concurrent (forkIO, threadDelay)
+import Control.Concurrent (ThreadId, myThreadId, threadDelay, killThread)
+import qualified Control.Concurrent as Concurrent
 
 import Data.Maybe
 import Control.Monad
@@ -46,12 +47,27 @@
         -> IO ()
 runQuit port modu func extras = runQuitWithReloadActions port modu func extras []
 
+-- |
+-- A version of `Concurrent.forkIO` that re-throws any uncaught
+-- `E.UserInterrupt` from the child thread in the parent thread.
+--
+-- We need this because using hint (or the GHC API) causes @UserInterrupt@ to
+-- occur in the thread that runs `runInterpreter` instead of the main thread.
+forkIO :: IO () -> IO ThreadId
+forkIO action = do
+  threadId <- myThreadId
+  Concurrent.forkIO (action `E.catch` \e -> case e of
+    E.UserInterrupt -> E.throwTo threadId e
+    _ -> E.throwIO e
+    )
+
 runQuitWithReloadActions :: Int -> ModuleName -> FunctionName -> (FilePath -> IO [FilePath])
                          -> [IO (IO ())] -> IO ()
 runQuitWithReloadActions port modu func extras actions = do
     sig <- newEmptyMVar
-    _ <- forkIO $ runWithReloadActions port modu func extras (Just sig) actions
-    go sig
+    mask $ \unmask -> do
+      threadId <- forkIO $ runWithReloadActions port modu func extras (Just sig) actions
+      unmask (go sig) `finally` killThread threadId
   where
     go sig = do
         x <- getLine
@@ -68,9 +84,9 @@
 runWithReloadActions port modu func extras msig initActions = do
     actions <- mapM id initActions
     ah <- initAppHolder
-    _ <- forkIO $ fillApp modu func extras ah msig actions 
-    Warp.run port $ toApp ah
-    return ()
+    mask $ \unmask -> do
+      threadId <- forkIO $ fillApp modu func extras ah msig actions
+      unmask (Warp.run port $ toApp ah) `finally` killThread threadId
 
 run :: Int -> ModuleName -> FunctionName -> (FilePath -> IO [FilePath]) -> Maybe (MVar ())
     -> IO ()
diff --git a/wai-handler-devel.cabal b/wai-handler-devel.cabal
--- a/wai-handler-devel.cabal
+++ b/wai-handler-devel.cabal
@@ -1,5 +1,5 @@
 Name:                wai-handler-devel
-Version:             1.3.0.5
+Version:             1.3.0.6
 Synopsis:            WAI server that automatically reloads code after modification.
 Description:         This handler automatically reloads your source code upon any changes. It works by using the hint package, essentially embedding GHC inside the handler. The handler (both the executable and library) takes three arguments: the port to listen on, the module name containing the application function, and the name of the function.
                      .
