nvbn/thefuck · error · TypeError

got an unexpected keyword argument '{}'

Error message

got an unexpected keyword argument '{}'

What it means

This TypeError is raised by thefuck's rule-helper is_app() (thefuck/utils.py) whenever it receives a keyword argument other than the single supported one, 'at_least'. is_app pops 'at_least' from kwargs and, if anything remains, raises with the leftover key names. It usually surfaces indirectly: the @for_app decorator forwards its own **kwargs straight into is_app, so any unsupported keyword passed to @for_app in a custom or third-party rule blows up here when the rule is evaluated against a command.

Source

Thrown at thefuck/utils.py:180

        else:
            if should_yield and line:
                yield line.strip()


def replace_command(command, broken, matched):
    """Helper for *_no_command rules."""
    new_cmds = get_close_matches(broken, matched, cutoff=0.1)
    return [replace_argument(command.script, broken, new_cmd.strip())
            for new_cmd in new_cmds]


@memoize
def is_app(command, *app_names, **kwargs):
    """Returns `True` if command is call to one of passed app names."""

    at_least = kwargs.pop('at_least', 0)
    if kwargs:
        raise TypeError("got an unexpected keyword argument '{}'".format(kwargs.keys()))

    if len(command.script_parts) > at_least:
        return os.path.basename(command.script_parts[0]) in app_names

    return False


def for_app(*app_names, **kwargs):
    """Specifies that matching script is for one of app names."""
    def _for_app(fn, command):
        if is_app(command, *app_names, **kwargs):
            return fn(command)
        else:
            return False

    return decorator(_for_app)

View on GitHub (pinned to c7e7e1d884)

Solutions

  1. Open the rule file named in the traceback and change the decorator to use only supported keywords: @for_app('prog', at_least=N) with no other kwargs; remove or delete unsupported ones.
  2. Check the spelling: the only accepted keyword is exactly 'at_least' (underscore, lowercase).
  3. If the rule came from a rules pack or blog post, check it against the thefuck version you have installed (git repo rules/ directory or CHANGELOG) and update the rule to that version's API.
  4. Upgrade/downgrade thefuck so its utils match the rule set you are using (pip install -U thefuck), then reload with `thefuck --alias` in a fresh shell.

Example fix

# before (~/.config/thefuck/rules/my_rule.py)
from thefuck.utils import for_app

@for_app('docker', atleast=2)   # misspelled keyword
def match(command):
    return 'is not a docker command' in command.output.lower()

# after
from thefuck.utils import for_app

@for_app('docker', at_least=2)  # only 'at_least' is supported
def match(command):
    return 'is not a docker command' in command.output.lower()
Defensive patterns

Strategy: validation

Validate before calling

# Validate rule decorators before thefuck loads them (e.g. in a rules smoke test):
import inspect
from thefuck.utils import for_app

SUPPORTED = {'at_least'}

def check_rule_kwargs(fn):
    # Re-create the kwargs your rule passes to for_app and verify them
    passed = getattr(fn, '_for_app_kwargs', {})  # set by your own wrapper if needed
    bad = set(passed) - SUPPORTED
    assert not bad, f"Unsupported @for_app kwargs: {bad}; only 'at_least' is valid"

# Simpler: grep your rules dir for misspellings before starting thefuck
import re, pathlib
for p in pathlib.Path('~/.config/thefuck/rules').expanduser().glob('*.py'):
    src = p.read_text()
    for m in re.finditer(r'@for_app\(([^)]*)\)', src):
        for kw in re.findall(r'(\w+)\s*=', m.group(1)):
            assert kw == 'at_least', f'{p.name}: bad kwarg {kw!r} in @for_app'

Type guard

# Python narrowing helper for direct is_app/for_app callers

def valid_for_app_kwargs(kwargs: dict) -> bool:
    """True iff kwargs are accepted by thefuck's is_app (only 'at_least')."""
    return set(kwargs) <= {'at_least'} and isinstance(kwargs.get('at_least', 0), int)

# usage before calling:
if not valid_for_app_kwargs({'atleast': 2}):
    raise TypeError('only at_least=<int> may be passed to @for_app')

Try / catch

# Wrap custom rule loading / correction so a bad rule reports itself instead of crashing:
def safe_match(rule_module, command):
    try:
        return rule_module.match(command)
    except TypeError as e:
        if 'unexpected keyword argument' in str(e):
            print(f"rule {rule_module.__name__} uses unsupported @for_app kwargs: {e}")
            return False  # let other rules still run
        raise

Prevention

When it happens

Trigger: Writing a thefuck rule with an unsupported decorator keyword, e.g. @for_app('apt-get', atleast=2) (misspelled) or @for_app('git', not_a_kwarg=True). The error also fires when calling utils.is_app(command, 'cargo', at_least=1, bogus=0) directly. Note the message interpolates kwargs.keys(), so on Python 3 it renders as "got an unexpected keyword argument 'dict_keys([...'bogus'])'" — the real offending key is inside that repr.

Common situations: Custom rules in ~/.config/thefuck/rules written against an older/newer thefuck API where decorator keywords changed; copy-pasted rules from blog posts or other thefuck versions that use keywords the installed version does not support; misspelling 'at_least' (e.g. 'atleast' or 'atLeast'); passing positional-arity hints that were removed. The traceback typically points at the rule module during `thefuck` correction matching, since for_app -> is_app runs for every candidate rule.


AI-assisted analysis of nvbn/thefuck@c7e7e1d884 (2026-08-14). Data as JSON: /api/errors/af370258b5116c11. Report an issue: GitHub.