diff --git a/CHANGES.txt b/CHANGES.txt
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,8 @@
 Changelog for Shake (* = breaking change)
 
+0.19.4, released 2021-01-14
+    #790, add option shakeAllowRedefineRules
+*   #790, remove overrideBuiltinRule
 0.19.3, released 2021-01-14
     #789, add overrideBuiltinRule
     #787, more documentation on doesFileExist
diff --git a/shake.cabal b/shake.cabal
--- a/shake.cabal
+++ b/shake.cabal
@@ -1,7 +1,7 @@
 cabal-version:      >= 1.18
 build-type:         Simple
 name:               shake
-version:            0.19.3
+version:            0.19.4
 license:            BSD3
 license-file:       LICENSE
 category:           Development, Shake
diff --git a/src/Development/Shake/Internal/Args.hs b/src/Development/Shake/Internal/Args.hs
--- a/src/Development/Shake/Internal/Args.hs
+++ b/src/Development/Shake/Internal/Args.hs
@@ -310,6 +310,8 @@
 shakeOptsEx :: [(Bool, OptDescr (Either String ([Extra], ShakeOptions -> ShakeOptions)))]
 shakeOptsEx =
     [opts $ Option "a" ["abbrev"] (reqArgPair "abbrev" "FULL=SHORT" $ \a s -> s{shakeAbbreviations=shakeAbbreviations s ++ [a]}) "Use abbreviation in status messages."
+    ,opts $ Option ""  ["allow-redefine-rules"] (noArg $ \s -> s{shakeAllowRedefineRules = True}) "Allow redefining built-in rules"
+    ,opts $ Option ""  ["no-allow-redefine-rules"] (noArg $ \s -> s{shakeAllowRedefineRules = False}) "Forbid redefining built-in rules (default)"
     ,extr $ Option ""  ["no-build"] (noArg [NoBuild]) "Don't build anything."
     ,extr $ Option "C" ["directory"] (reqArg "DIRECTORY" $ \x -> [ChangeDirectory x]) "Change to DIRECTORY before doing anything."
 --    ,yes $ Option ""  ["cloud"] (reqArg "URL" $ \x s -> s{shakeCloud=shakeCloud s ++ [x]}) "HTTP server providing a cloud cache."
diff --git a/src/Development/Shake/Internal/Core/Rules.hs b/src/Development/Shake/Internal/Core/Rules.hs
--- a/src/Development/Shake/Internal/Core/Rules.hs
+++ b/src/Development/Shake/Internal/Core/Rules.hs
@@ -6,8 +6,7 @@
 
 module Development.Shake.Internal.Core.Rules(
     Rules, SRules(..), runRules,
-    RuleResult,
-    addBuiltinRule, overrideBuiltinRule, addBuiltinRuleEx,
+    RuleResult, addBuiltinRule, addBuiltinRuleEx,
     noLint, noIdentity,
     getShakeOptionsRules,
     getUserRuleInternal, getUserRuleOne, getUserRuleList, getUserRuleMaybe,
@@ -130,10 +129,10 @@
 
 runRules :: ShakeOptions -> Rules () -> IO (SRules [])
 runRules opts (Rules r) = do
-    ref <- newIORef mempty
+    ref <- newIORef mempty{allowOverwrite = shakeAllowRedefineRules opts}
     runReaderT r (opts, ref)
     SRules{..} <- readIORef ref
-    pure $ SRules (runListBuilder actions) builtinRules userRules (runListBuilder targets) (runListBuilder helpSuffix) override
+    pure $ SRules (runListBuilder actions) builtinRules userRules (runListBuilder targets) (runListBuilder helpSuffix) allowOverwrite
 
 -- | Get all targets registered in the given rules. The names in
 --   'Development.Shake.phony' and 'Development.Shake.~>' as well as the file patterns
@@ -161,20 +160,20 @@
     ,userRules :: !(TMap.Map UserRuleVersioned)
     ,targets :: !(list Target)
     ,helpSuffix :: !(list String)
-    ,override :: Bool
+    ,allowOverwrite :: Bool
     }
 
 instance Semigroup (SRules ListBuilder) where
     (SRules x1 x2 x3 x4 x5 x6) <> (SRules y1 y2 y3 y4 y5 y6) =
-      SRules (mappend x1 y1) (Map.unionWithKey f x2 y2) (TMap.unionWith (<>) x3 y3) (mappend x4 y4) (mappend x5 y5) pleaseOverwrite
+      SRules (mappend x1 y1) (Map.unionWithKey f x2 y2) (TMap.unionWith (<>) x3 y3) (mappend x4 y4) (mappend x5 y5) canOverwrite
       where
-        pleaseOverwrite = x6 || y6
+        canOverwrite = x6 && y6
         f k a b
-          | pleaseOverwrite = b
+          | canOverwrite = b
           | otherwise = throwImpure $ errorRuleDefinedMultipleTimes k [builtinLocation a, builtinLocation b]
 
 instance Monoid (SRules ListBuilder) where
-    mempty = SRules mempty Map.empty TMap.empty mempty mempty False
+    mempty = SRules mempty Map.empty TMap.empty mempty mempty True
     mappend = (<>)
 
 instance Semigroup a => Semigroup (Rules a) where
@@ -227,19 +226,6 @@
 --   See 'addBuiltinRule' and 'Development.Shake.Rule.apply'.
 type family RuleResult key -- = value
 
-addBuiltinRuleCanOverride ::
-  (RuleResult key ~ value, ShakeValue key, Typeable value, NFData value, Show value, Partial) =>
-  Bool ->
-  BuiltinLint key value ->
-  BuiltinIdentity key value ->
-  BuiltinRun key value ->
-  Rules ()
-addBuiltinRuleCanOverride canOverride =
-  withFrozenCallStack $
-    addBuiltinRuleInternal canOverride $
-      BinaryOp
-        (putEx . Bin.toLazyByteString . execPut . put)
-        (runGet get . LBS.fromChunks . pure)
 -- | Before looking at this function, you should read the warnings at the top of this module.
 --   This function is not often necessary in build systems.
 --
@@ -254,39 +240,27 @@
 addBuiltinRule
     :: (RuleResult key ~ value, ShakeValue key, Typeable value, NFData value, Show value, Partial)
     => BuiltinLint key value -> BuiltinIdentity key value -> BuiltinRun key value -> Rules ()
-addBuiltinRule = addBuiltinRuleCanOverride False
-
--- | Like 'addBuiltinRule' but doesn't raise an error if a rule already exists.
---   In most cases 'addBuiltinRule' should be used.
-overrideBuiltinRule ::
-  (RuleResult key ~ value, ShakeValue key, Typeable value, NFData value, Show value, Partial) =>
-  BuiltinLint key value ->
-  BuiltinIdentity key value ->
-  BuiltinRun key value ->
-  Rules ()
-overrideBuiltinRule = addBuiltinRuleCanOverride True
+addBuiltinRule = withFrozenCallStack $ addBuiltinRuleInternal $ BinaryOp
+    (putEx . Bin.toLazyByteString . execPut . put)
+    (runGet get . LBS.fromChunks . pure)
 
 addBuiltinRuleEx
     :: (RuleResult key ~ value, ShakeValue key, BinaryEx key, Typeable value, NFData value, Show value, Partial)
     => BuiltinLint key value -> BuiltinIdentity key value -> BuiltinRun key value -> Rules ()
-addBuiltinRuleEx = addBuiltinRuleInternal False $ BinaryOp putEx getEx
+addBuiltinRuleEx = addBuiltinRuleInternal $ BinaryOp putEx getEx
 
 
 -- | Unexpected version of 'addBuiltinRule', which also lets me set the 'BinaryOp'.
 addBuiltinRuleInternal
     :: (RuleResult key ~ value, ShakeValue key, Typeable value, NFData value, Show value, Partial)
-    => Bool -> BinaryOp key -> BuiltinLint key value -> BuiltinIdentity key value -> BuiltinRun key value -> Rules ()
-addBuiltinRuleInternal override binary lint check (run :: BuiltinRun key value) = do
+    => BinaryOp key -> BuiltinLint key value -> BuiltinIdentity key value -> BuiltinRun key value -> Rules ()
+addBuiltinRuleInternal binary lint check (run :: BuiltinRun key value) = do
     let k = Proxy :: Proxy key
     let lint_ k v = lint (fromKey k) (fromValue v)
     let check_ k v = check (fromKey k) (fromValue v)
     let run_ k v b = fmap newValue <$> run (fromKey k) v b
     let binary_ = BinaryOp (putOp binary . fromKey) (newKey . getOp binary)
-    newRules
-      mempty
-        { builtinRules = Map.singleton (typeRep k) $ BuiltinRule lint_ check_ run_ binary_ (Ver 0) callStackTop,
-          override
-        }
+    newRules mempty{builtinRules = Map.singleton (typeRep k) $ BuiltinRule lint_ check_ run_ binary_ (Ver 0) callStackTop}
 
 
 -- | Change the priority of a given set of rules, where higher values take precedence.
diff --git a/src/Development/Shake/Internal/Options.hs b/src/Development/Shake/Internal/Options.hs
--- a/src/Development/Shake/Internal/Options.hs
+++ b/src/Development/Shake/Internal/Options.hs
@@ -214,6 +214,8 @@
     ,shakeNeedDirectory :: Bool
         -- ^ Defaults to @False@. Is depending on a directory an error (default), or it is permitted with
         --   undefined results. Provided for compatibility with @ninja@.
+    ,shakeAllowRedefineRules :: Bool
+        -- ^ Whether to allow calling addBuiltinRule for the same key more than once
     ,shakeProgress :: IO Progress -> IO ()
         -- ^ Defaults to no action. A function called when the build starts, allowing progress to be reported.
         --   The function is called on a separate thread, and that thread is killed when the build completes.
@@ -238,7 +240,7 @@
 shakeOptions :: ShakeOptions
 shakeOptions = ShakeOptions
     ".shake" 1 "1" Info False [] Nothing [] [] [] [] (Just 10) [] [] False True False
-    True ChangeModtime True [] False False Nothing [] False False
+    True ChangeModtime True [] False False Nothing [] False False False
     (const $ pure ())
     (const $ BS.putStrLn . UTF8.fromString) -- try and output atomically using BS
     (\_ _ _ -> pure ())
@@ -250,20 +252,20 @@
     ,"shakeFlush", "shakeRebuild", "shakeAbbreviations", "shakeStorageLog"
     ,"shakeLineBuffering", "shakeTimings", "shakeRunCommands", "shakeChange", "shakeCreationCheck"
     ,"shakeLiveFiles", "shakeVersionIgnore", "shakeColor", "shakeShare", "shakeCloud", "shakeSymlink"
-    ,"shakeNeedDirectory"
+    ,"shakeNeedDirectory", "shakeCanRedefineRules"
     ,"shakeProgress", "shakeOutput", "shakeTrace", "shakeExtra"]
 tyShakeOptions = mkDataType "Development.Shake.Types.ShakeOptions" [conShakeOptions]
 conShakeOptions = mkConstr tyShakeOptions "ShakeOptions" fieldsShakeOptions Prefix
-unhide x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 y1 y2 y3 y4 =
-    ShakeOptions x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27
+unhide x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 x28 y1 y2 y3 y4 =
+  ShakeOptions x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 x28
         (fromHidden y1) (fromHidden y2) (fromHidden y3) (fromHidden y4)
 
 instance Data ShakeOptions where
-    gfoldl k z (ShakeOptions x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 y1 y2 y3 y4) =
+    gfoldl k z (ShakeOptions x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19 x20 x21 x22 x23 x24 x25 x26 x27 x28 y1 y2 y3 y4) =
         z unhide `k` x1 `k` x2 `k` x3 `k` x4 `k` x5 `k` x6 `k` x7 `k` x8 `k` x9 `k` x10 `k` x11 `k`
-        x12 `k` x13 `k` x14 `k` x15 `k` x16 `k` x17 `k` x18 `k` x19 `k` x20 `k` x21 `k` x22 `k` x23 `k` x24 `k` x25 `k` x26 `k` x27 `k`
+        x12 `k` x13 `k` x14 `k` x15 `k` x16 `k` x17 `k` x18 `k` x19 `k` x20 `k` x21 `k` x22 `k` x23 `k` x24 `k` x25 `k` x26 `k` x27 `k` x28 `k`
         Hidden y1 `k` Hidden y2 `k` Hidden y3 `k` Hidden y4
-    gunfold k z _ = k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ z unhide
+    gunfold k z _ = k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ k $ z unhide
     toConstr ShakeOptions{} = conShakeOptions
     dataTypeOf _ = tyShakeOptions
 
diff --git a/src/Development/Shake/Rule.hs b/src/Development/Shake/Rule.hs
--- a/src/Development/Shake/Rule.hs
+++ b/src/Development/Shake/Rule.hs
@@ -16,7 +16,7 @@
 
     -- * Defining builtin rules
     -- | Functions and types for defining new types of Shake rules.
-    addBuiltinRule, overrideBuiltinRule,
+    addBuiltinRule,
     BuiltinLint, noLint, BuiltinIdentity, noIdentity, BuiltinRun, RunMode(..), RunChanged(..), RunResult(..),
     -- * Calling builtin rules
     -- | Wrappers around calling Shake rules. In general these should be specialised to a builtin rule.
diff --git a/src/Test/BuiltinOverride.hs b/src/Test/BuiltinOverride.hs
--- a/src/Test/BuiltinOverride.hs
+++ b/src/Test/BuiltinOverride.hs
@@ -24,13 +24,15 @@
   addBuiltinRule noLint noIdentity $ \(Key n) _ _ -> do
     liftIO $ putMVar resultsStore n
     pure $ RunResult ChangedRecomputeDiff mempty ()
-  overrideBuiltinRule noLint noIdentity $ \(Key n) _ _ -> do
+  addBuiltinRule noLint noIdentity $ \(Key n) _ _ -> do
     liftIO $ putMVar resultsStore (n + 1)
     pure $ RunResult ChangedRecomputeDiff mempty ()
   action $ apply1 $ Key 1
 
 test store build = do
-  build []
+  build ["--allow-redefine-rules"]
 
   res <- takeMVar store
   assertBool (res == 2) "Rule was not overriden"
+
+  assertException ["rule defined twice"] $ build ["--quiet"]
