Skip to content

Bool-syntax flags whose name starts with "no" are silently renamed and inverted for functions accepting **kwargs #697

Description

@Poojax21

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions