How the Rule Matching Process Works in TheFuck: From Failed Command to Fix

TheFuck's rule matching pipeline executes four sequential stages: wrapping the failed terminal command in a Command object, dynamically loading available rule modules from bundled and user directories, filtering for enabled rules via global settings, and testing each rule's match function to generate prioritized corrections.

The rule matching process is the core algorithm powering nvbn/thefuck, the popular command-line tool that suggests corrections for mistyped or failed terminal commands. Implemented across thefuck/types.py, thefuck/corrector.py, and thefuck/conf.py, this engine evaluates Python-based rules against the failed command context to produce executable fixes.

Stage 1: Wrapping the Failed Command in a Command Object

The pipeline initiates in thefuck/types.py where Command.from_raw_script (lines 71‑82) instantiates a Command object. This class encapsulates the raw script array, captured stdout/stderr output, and helper properties like script_parts which tokenizes the command for downstream analysis. The Command instance serves as the immutable input context for all subsequent rule evaluations.

Stage 2: Discovering and Loading Rule Modules

Rule discovery occurs in thefuck/corrector.py through two key functions. First, get_rules_import_paths (lines 22‑38) yields filesystem paths for:

  • The bundled rules directory distributed with the package
  • The user's custom rules directory in their configuration path
  • Any installed third-party packages prefixed with thefuck_contrib_

Next, get_loaded_rules (lines 8‑19) iterates these paths and invokes Rule.from_path (lines 31‑53 in thefuck/types.py) for each Python file. This method uses load_source to dynamically import the module, then extracts the rule's match function, get_new_command generator, priority value, and requires_output flag to construct a typed Rule object.

Stage 3: Filtering Enabled Rules

Before pattern matching executes, Rule.is_enabled (lines 55‑66 in thefuck/types.py) applies configuration filters. The method consults the global settings object loaded from thefuck/conf.py, checking whether the rule name appears in settings.rules or if the ALL_ENABLED flag from thefuck/const.py is active. Users customize this behavior via the THEFUCK_RULES environment variable or the exclude_rules configuration option.

Stage 4: Executing Match Logic and Generating Corrections

For each enabled rule, the engine invokes Rule.is_match (lines 168‑178 in thefuck/types.py), which implements a defensive three-step validation:

  1. Output requirement check: If the rule specifies requires_output = True but command.output is None, the function returns False immediately
  2. Pattern matching: The rule's custom match(command) function executes inside a try/except block to catch runtime errors
  3. Command generation: Upon a True result, Rule.get_corrected_commands (lines 185‑199) calls the rule's get_new_command generator and wraps each suggestion in a CorrectedCommand object with calculated priority

For example, thefuck/rules/git_commit_add.py (lines 5‑10) implements match by verifying that "commit" exists in command.script_parts and that the error text contains the substring "no changes added to commit".

Aggregating and Prioritizing Suggestions

The get_corrected_commands function in thefuck/corrector.py (lines 81‑92) aggregates all matched rules into a generator yielding CorrectedCommand instances. Subsequently, organize_commands (lines 52‑78) deduplicates identical suggestions and sorts them by the rule's priority attribute. The final ordered list surfaces the most likely fix first when the user executes the fuck command.

Programmatic Rule Matching Example

You can interact directly with the rule matching process using the internal API:

from thefuck.types import Command
from thefuck.corrector import get_corrected_commands

# Simulate a failed git commit without staged changes

raw_script = ['git', 'commit']
output = ('On branch master\n'
          'nothing to commit, working tree clean\n'
          'no changes added to commit (use "git add" and/or "git commit -a")')

cmd = Command.from_raw_script(raw_script)

# Retrieve all corrected commands suggested by enabled rules

for corrected in get_corrected_commands(cmd):
    print('Suggested fix:', corrected.script)

Executing this script outputs suggestions such as git commit -a and git commit -p, generated when the git_commit_add rule's match function returns True based on the simulated error context.

Summary

  • Command encapsulation: Command.from_raw_script in thefuck/types.py (lines 71‑82) creates an object containing the script and terminal output.
  • Dynamic discovery: get_rules_import_paths in thefuck/corrector.py (lines 22‑38) locates rules in bundled, user, and third-party directories.
  • Configuration filtering: Rule.is_enabled checks settings.rules from thefuck/conf.py and the ALL_ENABLED flag from thefuck/const.py.
  • Defensive matching: Rule.is_match validates requires_output and executes the rule's match function inside exception handling.
  • Result optimization: organize_commands in thefuck/corrector.py (lines 52‑78) sorts deduplicated suggestions by priority before display.

Frequently Asked Questions

How does TheFuck determine which rules are enabled?

The Rule.is_enabled method in thefuck/types.py (lines 55‑66) evaluates the global settings.rules dictionary managed by thefuck/conf.py. Rules are active if explicitly included in the configuration or if the ALL_ENABLED constant from thefuck/const.py is set. Users override this via the THEFUCK_RULES environment variable or the exclude_rules config option.

What happens when multiple rules match the same command?

When multiple rules return True from their match functions, get_corrected_commands in thefuck/corrector.py (lines 81‑92) collects all suggestions. The organize_commands function (lines 52‑78) then removes duplicates and sorts results by the rule's priority attribute, ensuring higher-priority corrections appear first.

Can individual rules require command output to function?

Yes, rules can define requires_output = True in their module. During matching, Rule.is_match (lines 168‑178 in thefuck/types.py) first checks if command.output is None. If a rule requires output but the command produced none, the matching logic short-circuits to False without executing the rule's match function.

Where are rule modules stored and how are they loaded?

Rule modules reside in three locations: the bundled thefuck/rules directory, the user's custom rules directory, and any installed thefuck_contrib_* packages. The get_rules_import_paths function in thefuck/corrector.py (lines 22‑38) discovers these paths, while Rule.from_path (lines 31‑53 in thefuck/types.py) dynamically imports each Python file using load_source and extracts the rule's callables and metadata.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →