diff --git a/Cabal-tests/Cabal-tests.cabal b/Cabal-tests/Cabal-tests.cabal
index e4a6ca71c2524a631d45305b7e5e5a4619b03553..eb9509f5e5df5c944046b59d06decef2a669a93e 100644
--- a/Cabal-tests/Cabal-tests.cabal
+++ b/Cabal-tests/Cabal-tests.cabal
@@ -31,6 +31,7 @@ test-suite unit-tests
     UnitTests.Distribution.Compat.Time
     UnitTests.Distribution.Described
     UnitTests.Distribution.PkgconfigVersion
+    UnitTests.Distribution.Simple.Command
     UnitTests.Distribution.Simple.Glob
     UnitTests.Distribution.Simple.Program.GHC
     UnitTests.Distribution.Simple.Program.Internal
diff --git a/Cabal-tests/tests/UnitTests.hs b/Cabal-tests/tests/UnitTests.hs
index 611e3dd5bdb8eb435d84752442e874e53a2474a6..9ef73b80d9b35d3917799a36d376fb1779e15eaf 100644
--- a/Cabal-tests/tests/UnitTests.hs
+++ b/Cabal-tests/tests/UnitTests.hs
@@ -16,6 +16,7 @@ import Distribution.Compat.Time
 import qualified UnitTests.Distribution.Compat.CreatePipe
 import qualified UnitTests.Distribution.Compat.Time
 import qualified UnitTests.Distribution.Compat.Graph
+import qualified UnitTests.Distribution.Simple.Command
 import qualified UnitTests.Distribution.Simple.Glob
 import qualified UnitTests.Distribution.Simple.Program.GHC
 import qualified UnitTests.Distribution.Simple.Program.Internal
@@ -49,6 +50,8 @@ tests mtimeChangeCalibrated =
         (UnitTests.Distribution.Compat.Time.tests mtimeChange)
     , testGroup "Distribution.Compat.Graph"
         UnitTests.Distribution.Compat.Graph.tests
+    , testGroup "Distribution.Simple.Command"
+        UnitTests.Distribution.Simple.Command.tests
     , testGroup "Distribution.Simple.Glob"
         UnitTests.Distribution.Simple.Glob.tests
     , UnitTests.Distribution.Simple.Program.GHC.tests
diff --git a/Cabal-tests/tests/UnitTests/Distribution/Simple/Command.hs b/Cabal-tests/tests/UnitTests/Distribution/Simple/Command.hs
new file mode 100644
index 0000000000000000000000000000000000000000..ed93409e938ef176327370088b6f21913caf6bce
--- /dev/null
+++ b/Cabal-tests/tests/UnitTests/Distribution/Simple/Command.hs
@@ -0,0 +1,44 @@
+module UnitTests.Distribution.Simple.Command
+    ( tests
+    ) where
+
+import Distribution.Simple.Command
+import qualified Distribution.Simple.Flag as Flag
+import Distribution.Simple.Setup (optionVerbosity)
+import qualified Distribution.Verbosity as Verbosity
+import Test.Tasty
+import Test.Tasty.HUnit
+
+argumentTests :: [TestTree]
+argumentTests =
+  [ testCase "parses verbosity successfully" $ do
+      let p = commandParseArgs cmdUI isGlobal ["-v2"]
+      assertEqual "expected verbose" (Right verbose) $ evalParse p
+  , testCase "handles argument parse error gracefully" $ do
+      let p = commandParseArgs cmdUI isGlobal ["-v=2"]
+      assertEqual "expected error" (Left "errors") $ evalParse p
+  ]
+  where
+    -- evaluate command parse result, to force possible exceptions in 'f'
+    evalParse p = case p of
+      CommandErrors _         -> Left "errors"
+      CommandHelp _           -> Left "help"
+      CommandList _           -> Left "list"
+      CommandReadyToGo (f, _) -> Right $ f Flag.NoFlag
+    verbose = Flag.Flag Verbosity.verbose
+    isGlobal = True
+    cmdUI = CommandUI
+      { commandName = "cmd"
+      , commandSynopsis = "the command"
+      , commandUsage = \name -> name ++ " cmd -v[N]"
+      , commandDescription = Nothing
+      , commandNotes = Nothing
+      , commandDefaultFlags = Flag.NoFlag
+      , commandOptions = const [ optField ]
+      }
+    optField = optionVerbosity id const
+        
+tests :: [TestTree]
+tests =
+  [ testGroup "option argument tests" argumentTests
+  ]
diff --git a/Cabal/src/Distribution/GetOpt.hs b/Cabal/src/Distribution/GetOpt.hs
index 4c3bcca09d726355f240905f94a4a0be1d7e2384..18352df87b0636d92623a973f18ef1ca58a04f4f 100644
--- a/Cabal/src/Distribution/GetOpt.hs
+++ b/Cabal/src/Distribution/GetOpt.hs
@@ -17,8 +17,9 @@
 -- * Line wrapping in the 'usageInfo' output, plus a more compact
 --   rendering of short options, and slightly less padding.
 --
--- If you want to take on the challenge of merging this with the GetOpt
--- from the base package then go for it!
+-- * Parsing of option arguments is allowed to fail.
+--
+-- * 'ReturnInOrder' argument order is removed.
 --
 {-# LANGUAGE TupleSections #-}
 {-# LANGUAGE NamedFieldPuns #-}
@@ -36,8 +37,34 @@ module Distribution.GetOpt (
 
 import Prelude ()
 import Distribution.Compat.Prelude
-import System.Console.GetOpt
-         ( ArgOrder(..), OptDescr(..), ArgDescr(..) )
+
+-- | What to do with options following non-options
+data ArgOrder a
+  = RequireOrder                -- ^ no option processing after first non-option
+  | Permute                     -- ^ freely intersperse options and non-options
+
+data OptDescr a =              -- description of a single options:
+   Option [Char]                --    list of short option characters
+          [String]              --    list of long option strings (without "--")
+          (ArgDescr a)          --    argument descriptor
+          String                --    explanation of option for user
+
+instance Functor OptDescr where
+    fmap f (Option a b argDescr c) = Option a b (fmap f argDescr) c
+
+-- | Describes whether an option takes an argument or not, and if so
+-- how the argument is parsed to a value of type @a@.
+--
+-- Compared to System.Console.GetOpt, we allow for parse errors.
+data ArgDescr a
+   = NoArg                   a                       -- ^   no argument expected
+   | ReqArg (String       -> Either String a) String -- ^   option requires argument
+   | OptArg (Maybe String -> Either String a) String -- ^   optional argument
+
+instance Functor ArgDescr where
+    fmap f (NoArg a)    = NoArg (f a)
+    fmap f (ReqArg g s) = ReqArg (fmap f . g) s
+    fmap f (OptArg g s) = OptArg (fmap f . g) s
 
 data OptKind a                -- kind of cmd line arg (internal use only):
    = Opt       a                --    an option
@@ -155,10 +182,8 @@ getOpt' ordering optDescr (arg:args) = procNextOpt opt ordering
          procNextOpt (UnreqOpt u) _                 = (os,xs,u:us,es)
          procNextOpt (NonOpt x)   RequireOrder      = ([],x:rest,[],[])
          procNextOpt (NonOpt x)   Permute           = (os,x:xs,us,es)
-         procNextOpt (NonOpt x)   (ReturnInOrder f) = (f x :os, xs,us,es)
          procNextOpt EndOfOpts    RequireOrder      = ([],rest,[],[])
          procNextOpt EndOfOpts    Permute           = ([],rest,[],[])
-         procNextOpt EndOfOpts    (ReturnInOrder f) = (map f rest,[],[],[])
          procNextOpt (OptErr e)   _                 = (os,xs,us,e:es)
 
          (opt,rest) = getNext arg args optDescr
@@ -181,15 +206,16 @@ longOpt ls rs optDescr = long ads arg rs
          options   = if null exact then getWith isPrefixOf else exact
          ads       = [ ad | Option _ _ ad _ <- options ]
          optStr    = "--" ++ opt
+         fromRes   = fromParseResult optStr
 
          long (_:_:_)      _        rest     = (errAmbig options optStr,rest)
          long [NoArg  a  ] []       rest     = (Opt a,rest)
          long [NoArg  _  ] ('=':_)  rest     = (errNoArg optStr,rest)
          long [ReqArg _ d] []       []       = (errReq d optStr,[])
-         long [ReqArg f _] []       (r:rest) = (Opt (f r),rest)
-         long [ReqArg f _] ('=':xs) rest     = (Opt (f xs),rest)
-         long [OptArg f _] []       rest     = (Opt (f Nothing),rest)
-         long [OptArg f _] ('=':xs) rest     = (Opt (f (Just xs)),rest)
+         long [ReqArg f _] []       (r:rest) = (fromRes (f r),rest)
+         long [ReqArg f _] ('=':xs) rest     = (fromRes (f xs),rest)
+         long [OptArg f _] []       rest     = (fromRes (f Nothing),rest)
+         long [OptArg f _] ('=':xs) rest     = (fromRes (f (Just xs)),rest)
          long _            _        rest     = (UnreqOpt ("--"++ls),rest)
 
 -- handle short option
@@ -198,15 +224,16 @@ shortOpt y ys rs optDescr = short ads ys rs
   where options = [ o  | o@(Option ss _ _ _) <- optDescr, s <- ss, y == s ]
         ads     = [ ad | Option _ _ ad _ <- options ]
         optStr  = '-':[y]
+        fromRes = fromParseResult optStr
 
         short (_:_:_)        _  rest     = (errAmbig options optStr,rest)
         short (NoArg  a  :_) [] rest     = (Opt a,rest)
         short (NoArg  a  :_) xs rest     = (Opt a,('-':xs):rest)
         short (ReqArg _ d:_) [] []       = (errReq d optStr,[])
-        short (ReqArg f _:_) [] (r:rest) = (Opt (f r),rest)
-        short (ReqArg f _:_) xs rest     = (Opt (f xs),rest)
-        short (OptArg f _:_) [] rest     = (Opt (f Nothing),rest)
-        short (OptArg f _:_) xs rest     = (Opt (f (Just xs)),rest)
+        short (ReqArg f _:_) [] (r:rest) = (fromRes (f r),rest)
+        short (ReqArg f _:_) xs rest     = (fromRes (f xs),rest)
+        short (OptArg f _:_) [] rest     = (fromRes (f Nothing),rest)
+        short (OptArg f _:_) xs rest     = (fromRes (f (Just xs)),rest)
         short []             [] rest     = (UnreqOpt optStr,rest)
         short []             xs rest     = (UnreqOpt (optStr++xs),rest)
         -- This is different vs upstream = (UnreqOpt optStr,('-':xs):rest)
@@ -215,9 +242,14 @@ shortOpt y ys rs optDescr = short ads ys rs
         -- But why was no equivalent change required for longOpt? So could
         -- this change go upstream?
 
+fromParseResult :: String -> Either String a -> OptKind a
+fromParseResult optStr res = case res of
+  Right x   -> Opt x
+  Left  err -> OptErr ("invalid argument to option `" ++ optStr ++ "': " ++ err ++ "\n")
+
 -- miscellaneous error formatting
 
-errAmbig :: [OptDescr a] -> String -> OptKind a
+errAmbig :: [OptDescr a] -> String -> OptKind b
 errAmbig ods optStr = OptErr (usageInfo header ods)
    where header = "option `" ++ optStr ++ "' is ambiguous; could be one of:"
 
diff --git a/Cabal/src/Distribution/ReadE.hs b/Cabal/src/Distribution/ReadE.hs
index 863049dea611a63b3975e7c698aad4acc6112d6b..ba278b947c9cd3fdaeddcaee2226c09d5c9999d5 100644
--- a/Cabal/src/Distribution/ReadE.hs
+++ b/Cabal/src/Distribution/ReadE.hs
@@ -13,7 +13,6 @@ module Distribution.ReadE (
    -- * ReadE
    ReadE(..), succeedReadE, failReadE,
    -- * Projections
-   readEOrFail,
    parsecToReadE,
   ) where
 
@@ -38,9 +37,6 @@ succeedReadE f = ReadE (Right . f)
 failReadE :: ErrorMsg -> ReadE a
 failReadE = ReadE . const . Left
 
-readEOrFail :: ReadE a -> String -> a
-readEOrFail r = either error id . runReadE r
-
 parsecToReadE :: (String -> ErrorMsg) -> ParsecParser a -> ReadE a
 parsecToReadE err p = ReadE $ \txt ->
     case runParsecParser p "<parsecToReadE>" (fieldLineStreamFromString txt) of
diff --git a/Cabal/src/Distribution/Simple/Command.hs b/Cabal/src/Distribution/Simple/Command.hs
index ec7870f2ceb130d093cd27bd4bf7197278aa293d..69e0a1976598966ad1e071c27a4c554523c485ac 100644
--- a/Cabal/src/Distribution/Simple/Command.hs
+++ b/Cabal/src/Distribution/Simple/Command.hs
@@ -223,16 +223,15 @@ commandGetOpts :: ShowOrParseArgs -> CommandUI flags
 commandGetOpts showOrParse command =
     concatMap viewAsGetOpt (commandOptions command showOrParse)
 
-viewAsGetOpt :: OptionField a -> [GetOpt.OptDescr (a->a)]
+viewAsGetOpt :: OptionField a -> [GetOpt.OptDescr (a -> a)]
 viewAsGetOpt (OptionField _n aa) = concatMap optDescrToGetOpt aa
   where
     optDescrToGetOpt (ReqArg d (cs,ss) arg_desc set _) =
-         [GetOpt.Option cs ss (GetOpt.ReqArg set' arg_desc) d]
-             where set' = readEOrFail set
+         [GetOpt.Option cs ss (GetOpt.ReqArg (runReadE set) arg_desc) d]
     optDescrToGetOpt (OptArg d (cs,ss) arg_desc set def _) =
          [GetOpt.Option cs ss (GetOpt.OptArg set' arg_desc) d]
-             where set' Nothing    = def
-                   set' (Just txt) = readEOrFail set txt
+             where set' Nothing    = Right def
+                   set' (Just txt) = runReadE set txt
     optDescrToGetOpt (ChoiceOpt alts) =
          [GetOpt.Option sf lf (GetOpt.NoArg set) d | (d,(sf,lf),set,_) <- alts ]
     optDescrToGetOpt (BoolOpt d (sfT, lfT) ([],  [])  set _) =
@@ -391,13 +390,8 @@ addCommonFlags :: ShowOrParseArgs
                -> [GetOpt.OptDescr a]
                -> [GetOpt.OptDescr (Either CommonFlag a)]
 addCommonFlags showOrParseArgs options =
-     map (fmapOptDesc Left)  (commonFlags showOrParseArgs)
-  ++ map (fmapOptDesc Right) options
-  where fmapOptDesc f (GetOpt.Option s l d m) =
-                       GetOpt.Option s l (fmapArgDesc f d) m
-        fmapArgDesc f (GetOpt.NoArg a)    = GetOpt.NoArg (f a)
-        fmapArgDesc f (GetOpt.ReqArg s d) = GetOpt.ReqArg (f . s) d
-        fmapArgDesc f (GetOpt.OptArg s d) = GetOpt.OptArg (f . s) d
+     map (fmap Left)  (commonFlags showOrParseArgs)
+  ++ map (fmap Right) options
 
 -- | Parse a bunch of command line arguments
 --
diff --git a/changelog.d/option-argument-errors b/changelog.d/option-argument-errors
new file mode 100644
index 0000000000000000000000000000000000000000..d2e9019019aca982c5b2e0d18159bafe74bb9c0b
--- /dev/null
+++ b/changelog.d/option-argument-errors
@@ -0,0 +1,9 @@
+synopsis: Handle option argument parse errors without 'error'
+packages: Cabal, cabal-install
+prs: #7579
+issues: #7573
+description: {
+- Errors parsing arguments such as `-v=3` no longer result in
+  stack traces.
+- `Distribution.ReadE.readEOrFail` was removed.
+}