Are you sure you need that pattern? --foo and --foo <value>, together, for a boolean switch, is not a common pattern to use.
As for your issue, remember that the command line value is a string and, type=bool means that you want bool(entered-string-value) to be applied. For --foo False that means bool("False"), producing True; all non-empty strings are true! See Why is argparse not parsing my boolean flag correctly? as well.
Instead of supporting --foo / --foo <string value>, I would strongly recommend you use --foo to mean True, drop the argument value, and instead add a --no-foo option to explicitly set False:
parser.add_argument('--foo', default=False, action='store_true')
parser.add_argument('--no-foo', dest='foo', action='store_false')
The dest='foo' addition on the --no-foo switch ensures that the False value it stores (via store_false) ends up on the same args.foo attribute.
As of Python 3.9, you can also use the argparse.BooleanOptionalAction action class:
parser.add_argument("--foo", action=argparse.BooleanOptionalAction)
and it'll have the same effect, handling --foo and --no-foo to set and clear the flag.
You'd only need a --foo / --no-foo combination if you have some other configuration mechanism that would set foo to True and you needed to override this again with a command-line switch. --no-<option> is a widely adopted standard to invert a boolean command-line switch.
If you don't have a specific need for a --no-foo inverted switch (since just omitting --foo would already mean 'false'), then just stick with the action='store_true' option. This keeps your command line simple and clear!
However, if your use case or other constraints specifically require that your command line must have some king of --foo (true|false|0|1) support, then add your own converter:
def str_to_bool(value):
    if isinstance(value, bool):
        return value
    if value.lower() in {'false', 'f', '0', 'no', 'n'}:
        return False
    elif value.lower() in {'true', 't', '1', 'yes', 'y'}:
        return True
    raise ValueError(f'{value} is not a valid boolean value')
parser.add_argument('--foo', type=str_to_bool, nargs='?', const=True, default=False)
- the constvalue is used fornargs='?'arguments where the argument value is omitted. Here that setsfoo=Truewhen--foois used.
- default=Falseis used when the switch is not used at all.
- type=str_to_boolis used to handle the- --foo <value>case.
Demo:
$ cat so52403065.py
from argparse import ArgumentParser
parser = ArgumentParser()
def str_to_bool(value):
    if value.lower() in {'false', 'f', '0', 'no', 'n'}:
        return False
    elif value.lower() in {'true', 't', '1', 'yes', 'y'}:
        return True
    raise ValueError(f'{value} is not a valid boolean value')
parser.add_argument('--foo', type=str_to_bool, nargs='?', const=True, default=False)
print(parser.parse_args())
$ python so52403065.py
Namespace(foo=False)
$ python so52403065.py --foo
Namespace(foo=True)
$ python so52403065.py --foo True
Namespace(foo=True)
$ python so52403065.py --foo no
Namespace(foo=False)
$ python so52403065.py --foo arrbuggrhellno
usage: so52403065.py [-h] [--foo [FOO]]
so52403065.py: error: argument --foo: invalid str_to_bool value: 'arrbuggrhellno'