Summary
In _ParseKeywordArgs (fire/core.py), when a function accepts **kwargs, any boolean-syntax flag whose key starts with no is treated as an negation of another argument: the no prefix is stripped from the keyword and the value becomes 'False'. As a result, a kwarg that genuinely starts with no (e.g. nothing, notes, nodes) can never be passed with bare bool syntax - it is silently renamed to its suffix and set to False.
This is a static-analysis finding based on reading master; I have not executed this code path in a live CLI.
Location
- File:
fire/core.py
- Function:
_ParseKeywordArgs
- Relevant code path (master, as of Aug 2026):
# Determine the keyword.
if (key in fn_args
or (is_bool_syntax and key.startswith('no') and key[2:] in fn_args)
or fn_keywords):
keyword = key
...
elif is_bool_syntax:
got_argument = True
if keyword in fn_args:
value = 'True'
elif keyword.startswith('no'):
keyword = keyword[2:]
value = 'False'
else:
value = 'True'
Problem
The no-prefix handling is intended for negating known arguments (--no-color ? color=False). The keyword-resolution step accepts the flag through the third branch (fn_keywords, i.e. the function has **kwargs) without checking that the no-stripped key actually exists in fn_args. The value-determination step then unconditionally strips no and assigns False whenever the keyword itself is not in fn_args. The two conditions are inconsistent:
- Keyword resolution: accepts
key because fn_keywords is truthy.
- Value determination: assumes
key must be either a known arg or a no-negation.
For a **kwargs function there is no known-arg relationship at all, so any no*-prefixed key falls into the negation branch by accident.
Trigger / Reproduction
Based on source analysis (not executed):
# cli.py
import fire
def run(**kwargs):
print(kwargs)
if __name__ == '__main__':
fire.Fire(run)
$ python cli.py --nothing
Expected: {'nothing': True}
Predicted by the code path above: {'thing': False}
The same applies to e.g. --notes ? {'tes': False}, --nodes ? {'des': False}. Passing --nothing=True works around it because the contains_equals path skips the negation logic entirely.
Expected Behavior
A flag passed with bare bool syntax should populate **kwargs under the exact name given ({'nothing': True}), unless the stripped key (key[2:]) matches an actual named argument of the function, in which case the documented --no-x negation behavior should apply.
Actual Behavior
The keyword is rewritten to key[2:] and the value forced to 'False', with no error or warning. Both the argument name and its value are wrong.
Impact
Any Fire CLI exposing a **kwargs passthrough (a common pattern for generic wrappers) silently misinterprets flags starting with no: users get misspelled kwargs and inverted values instead of either the requested behavior or an error message. Because nothing crashes, the mistake is easy to miss in scripts.
Suggested Direction
Only treat the no prefix as negation when key[2:] resolves to a known entry in fn_args (mirroring the guard used during keyword resolution). Keys accepted solely because of fn_keywords should keep their literal name and default bool value True.
Evidence
_IsFlag('--nothing') is true (multi-char flag), and with no following non-flag token is_bool_syntax is true.
- Keyword resolution sets
keyword = 'nothing' via fn_keywords.
- The subsequent
elif keyword.startswith('no') branch then rewrites keyword = 'thing' and value = 'False'; kwargs[keyword] = value stores the corrupted pair.
Summary
In
_ParseKeywordArgs(fire/core.py), when a function accepts**kwargs, any boolean-syntax flag whose key starts withnois treated as an negation of another argument: thenoprefix is stripped from the keyword and the value becomes'False'. As a result, a kwarg that genuinely starts withno(e.g.nothing,notes,nodes) can never be passed with bare bool syntax - it is silently renamed to its suffix and set toFalse.This is a static-analysis finding based on reading
master; I have not executed this code path in a live CLI.Location
fire/core.py_ParseKeywordArgsProblem
The
no-prefix handling is intended for negating known arguments (--no-color?color=False). The keyword-resolution step accepts the flag through the third branch (fn_keywords, i.e. the function has**kwargs) without checking that theno-stripped key actually exists infn_args. The value-determination step then unconditionally stripsnoand assignsFalsewhenever the keyword itself is not infn_args. The two conditions are inconsistent:keybecausefn_keywordsis truthy.keymust be either a known arg or ano-negation.For a
**kwargsfunction there is no known-arg relationship at all, so anyno*-prefixed key falls into the negation branch by accident.Trigger / Reproduction
Based on source analysis (not executed):
Expected:
{'nothing': True}Predicted by the code path above:
{'thing': False}The same applies to e.g.
--notes?{'tes': False},--nodes?{'des': False}. Passing--nothing=Trueworks around it because thecontains_equalspath skips the negation logic entirely.Expected Behavior
A flag passed with bare bool syntax should populate
**kwargsunder the exact name given ({'nothing': True}), unless the stripped key (key[2:]) matches an actual named argument of the function, in which case the documented--no-xnegation behavior should apply.Actual Behavior
The keyword is rewritten to
key[2:]and the value forced to'False', with no error or warning. Both the argument name and its value are wrong.Impact
Any Fire CLI exposing a
**kwargspassthrough (a common pattern for generic wrappers) silently misinterprets flags starting withno: users get misspelled kwargs and inverted values instead of either the requested behavior or an error message. Because nothing crashes, the mistake is easy to miss in scripts.Suggested Direction
Only treat the
noprefix as negation whenkey[2:]resolves to a known entry infn_args(mirroring the guard used during keyword resolution). Keys accepted solely because offn_keywordsshould keep their literal name and default bool valueTrue.Evidence
_IsFlag('--nothing')is true (multi-char flag), and with no following non-flag tokenis_bool_syntaxis true.keyword = 'nothing'viafn_keywords.elif keyword.startswith('no')branch then rewriteskeyword = 'thing'andvalue = 'False';kwargs[keyword] = valuestores the corrupted pair.