Python argparse custom error message (tested)

argparse gives you four ways to control its error messages, and the most obvious one (raising ValueError with a nice message from your type= function) silently throws your message away. We measured all four on Python 3.13.5.

1. Your own checks: parser.error()

import argparse, sys
p = argparse.ArgumentParser(prog="tool")
p.add_argument("--start", type=int); p.add_argument("--end", type=int)
a = p.parse_args(sys.argv[1:])
if a.start is not None and a.end is not None and a.start > a.end:
    p.error(f"--start ({a.start}) must be <= --end ({a.end})")
print("ok", a)

Output of python3 b1.py --start 5 --end 2; echo exit code $?:

usage: tool [-h] [--start START] [--end END]
tool: error: --start (5) must be <= --end (2)
exit code 2

Measured on Python 3.13.5: parser.error(msg) prints the usage line and prog: error: msg to stderr and exits with code 2, exactly like argparse's built-in errors. Use it for checks argparse can't express (one option relative to another) instead of print() + sys.exit(), so all errors look the same.

2. Custom message for a bad value: raise ArgumentTypeError, not ValueError

import argparse
def port(s):
    n = int(s)                      # ValueError text is thrown away
    if not 1 <= n <= 65535:
        raise argparse.ArgumentTypeError(f"{n} is not a valid port (1-65535)")
    return n
def port_strict(s):
    if not s.isdigit():
        raise ValueError(f"'{s}' is not a number, dummy")   # also discarded
    return int(s)
p = argparse.ArgumentParser(prog="tool")
p.add_argument("--port", type=port); p.add_argument("--port2", type=port_strict)
for argv in (["--port", "99999"], ["--port", "abc"], ["--port2", "abc"]):
    try:
        p.parse_args(argv)
    except SystemExit as e:
        print("exit code", e.code)

Output:

usage: tool [-h] [--port PORT] [--port2 PORT2]
tool: error: argument --port: 99999 is not a valid port (1-65535)
exit code 2
usage: tool [-h] [--port PORT] [--port2 PORT2]
tool: error: argument --port: invalid port value: 'abc'
exit code 2
usage: tool [-h] [--port PORT] [--port2 PORT2]
tool: error: argument --port2: invalid port_strict value: 'abc'
exit code 2

Measured: only argparse.ArgumentTypeError keeps your message. A ValueError from your type function, even one with a custom message, is replaced by the generic invalid <function name> value: 'abc'. Since argparse puts the function's name in that message, name type functions for the user (port, not port_strict or validate_p).

3. Change the format or exit code: override error()

import argparse, sys
class MyParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write(f"tool: {message}\nRun 'tool --help' for usage.\n")
        sys.exit(1)
p = MyParser(prog="tool")
p.add_argument("name")
try:
    p.parse_args([])
except SystemExit as e:
    print("exit code", e.code)

Output:

tool: the following arguments are required: name
Run 'tool --help' for usage.
exit code 1

Measured: subclassing ArgumentParser and overriding error() changes every argparse error message (here: no usage dump, a help hint instead, exit code 1). error() must not return: it has to exit or raise. Sub-commands are covered too: add_subparsers() creates sub-parsers of the same class by default (we checked: a missing argument to a sub-command went through the override and exited with code 1).

4. Raise instead of exiting: exit_on_error=False

import argparse
p = argparse.ArgumentParser(prog="tool", exit_on_error=False)
p.add_argument("--n", type=int)
try:
    p.parse_args(["--n", "x"])
except argparse.ArgumentError as e:
    print("caught ArgumentError:", e)
try:
    p.error("custom check failed")
except SystemExit as e:
    print("p.error still exits:", e.code)

Output:

caught ArgumentError: argument --n: invalid int value: 'x'
usage: tool [-h] [--n N]
tool: error: custom check failed
p.error still exits: 2

Measured: with exit_on_error=False a bad value raises argparse.ArgumentError, which you can catch. Your own p.error() calls still print and exit with code 2. To make those raise too, use the error() override from section 3 and raise from it. For unrecognised or missing arguments see our argparse unrecognized/required guide.

Frequently asked questions

How do I raise a custom error in argparse?

Call parser.error('message'). We measured it printing usage plus 'prog: error: message' and exiting with code 2, the same as argparse's own errors.

How do I customize the argparse error message for an invalid value?

Raise argparse.ArgumentTypeError('...') in your type function. We measured a ValueError's custom message being discarded and replaced with 'invalid <function name> value'.

How do I change the argparse error format or exit code?

Subclass ArgumentParser and override error(self, message); it must exit or raise. We measured a version printing a help hint and exiting with code 1.

How do I stop argparse from exiting on error?

Pass exit_on_error=False to catch argparse.ArgumentError for bad values. We measured that parser.error() still exits with code 2, so override error() if you need it to raise.

What exit code does argparse use on error?

2. We measured exit code 2 for parser.error() and for invalid values.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5: parser.error() for a cross-option check, ArgumentTypeError vs ValueError in a type function, an ArgumentParser subclass overriding error(), and exit_on_error=False with a bad value and with parser.error(). Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.