The Fuck is a magnificent terminal companion that corrects errors in your previous console command. Type the wrong thing? Just type
fuckand let it suggest — and run — the right one.
1. Quick Start
The Fuck reads the most recent failed command, runs it through a catalog of more than 160 correction rules, and offers the most likely fix. Press Enter to run it; press up/down to cycle through alternatives; press Ctrl+C to abort.
Key features
- Shell-agnostic alias. Works in Bash, Zsh, Fish, Tcsh, and PowerShell — one command (
thefuck --alias) emits the right wrapper for whichever shell you are in. - 160+ built-in correction rules for Git, Docker, npm, pip, Homebrew, apt, dnf, pacman, AWS CLI, Azure CLI, Heroku, Vagrant, Terraform, systemd, Maven, Gradle, Yarn, and many more.
- Plugin architecture. Drop a Python file into
~/.config/thefuck/rules/and it joins the rotation. Third-party packages namedthefuck_contrib_*are auto-discovered too. - Multi-suggestion UI. When several rules match, browse the alternatives without retyping anything.
- Side-effect aware. Some fixes can do extra cleanup (delete a bad SSH key line, prune a half-extracted tarball) — the prompt marks those with
(+side effect). - History rewriting. The corrected command is written into your shell history so the up-arrow brings up the fix, not the typo (Bash, Zsh, Fish, Tcsh).
- Configurable via a Python file at
~/.config/thefuck/settings.pyand viaTHEFUCK_*environment variables.
Prerequisites
Python 3.5 or later (Python 2.7 is also supported but deprecated), a supported shell, and pip 6.0+.
Install in one line
pip install --user thefuck
Then add the alias to your shell config and reload. For example, in ~/.zshrc:
eval $(thefuck --alias)
Tip
You can rename the alias to anything you like —
eval $(thefuck --alias FUCK)gives youFUCKin caps;eval $(thefuck --alias please)gives a polite alternative. The Fuck respects theTF_ALIASenvironment variable internally so all rules keep working.
A first taste
$ git brnch
git: 'brnch' is not a git command. See 'git --help'.
The most similar command is
branch
$ fuck
git branch [enter/↑/↓/ctrl+c]
* main
feature/login
You typed git brnch, Git complained, you typed fuck, and the prompt now offers git branch. Hit Enter and it runs.
What to try next
- Read 2. Installation for package-manager-specific recipes (Homebrew, apt, pkg, scoop, AUR).
- See 4. Core Usage for the full CLI flag reference and the keyboard controls in the suggestion prompt.
- Browse 5. Rule Catalog to learn which mistakes The Fuck already knows how to fix.
- Skim 6. Common Tasks for recipes — auto-confirm, retry on failure, instant-mode, custom rules.
2. Installation
System requirements
| Requirement | Detail |
|---|---|
| Python | 2.7 or 3.5+ (Python 3.5+ recommended; 2.7 is deprecated) |
pip |
6.0 or newer (older versions are explicitly rejected by setup.py) |
| Shell | Bash, Zsh, Fish, Tcsh/Csh, or PowerShell/PWSH |
| OS | Linux, macOS, Windows, BSD; FreeBSD/OpenBSD work via pkg/ports |
| Runtime deps | psutil, colorama, six; on Windows also win_unicode_console |
| Optional | pyte and a working script(1) binary for experimental instant mode |
Install methods
pip (any platform)
pip install --user thefuck
Homebrew (macOS / Linux)
brew install thefuck
Debian / Ubuntu
sudo apt update
sudo apt install python3-dev python3-pip python3-setuptools
pip3 install --user thefuck
Fedora / RHEL
sudo dnf install python3-devel python3-pip
pip3 install --user thefuck
Arch Linux
sudo pacman -S thefuck
FreeBSD
pkg install thefuck
Source
git clone https://github.com/nvbn/thefuck.git
cd thefuck
pip install --user .
Important
pip install --userputsthefuckandfuckunder~/.local/bin(Linux/macOS) or%APPDATA%\Python\PythonX\Scripts(Windows). Iffuckis "not found" after install, that directory is not on your$PATH— see the troubleshooting entry below.
Wire up the shell alias
The Fuck does not modify your shell config automatically on pip install. You add a single line, then reload.
Bash
Append to ~/.bashrc (or ~/.bash_profile on macOS):
eval "$(thefuck --alias)"
Reload:
source ~/.bashrc
Zsh
Append to ~/.zshrc:
eval $(thefuck --alias)
Reload:
source ~/.zshrc
Fish
Append to ~/.config/fish/config.fish:
thefuck --alias | source
Reload:
fish
Tcsh / Csh
Append to ~/.tcshrc:
eval `thefuck --alias`
Reload by exec'ing tcsh.
PowerShell
Append to $profile:
iex "$(thefuck --alias)"
Reload:
. $profile
Tip
You can also let The Fuck configure itself: type
fuckonce with no alias set and it prints how-to-configure instructions. Typefucka second time within 60 seconds (controlled by the internalCONFIGURATION_TIMEOUTconstant) and it appends the alias line for you in supported shells. Thensourcethe file or restart the shell.
Verify the install
fuck --version
The Fuck 3.32 using Python 3.11.4 and Bash 5.2.15
If you see the version banner — alias resolved, Python ABI is happy, and the shell detection landed on a known shell — installation is complete.
Install troubleshooting
Error: fuck: command not found after install
Cause: pip install --user puts the script in a per-user directory that is not on PATH by default.
Solution:
- Find the directory:
python3 -m site --user-basethen append/bin(Linux/macOS) or\Scripts(Windows). - Add it to
PATH. On Bash/Zsh, append to your rc file:export PATH="$HOME/.local/bin:$PATH" - Restart the shell.
Prevention: Use pipx install thefuck if you prefer isolated CLIs, or install via Homebrew/apt/dnf where the package places binaries on PATH automatically.
Error: ModuleNotFoundError: No module named 'imp' (Python 3.12+)
Cause: Python 3.12 removed the legacy imp module that older releases of The Fuck imported. (Tracked in upstream issues #1434 and #1444.)
Solution:
- Upgrade to the latest release:
pip install --user --upgrade thefuck. - If still failing, downgrade your Python or use
pyenv install 3.11.xand pointpipat it.
Prevention: Pin a Python version known to work in your CI (3.10 or 3.11 are safest as of this writing).
Error: ModuleNotFoundError: No module named 'distutils' (Python 3.12+)
Cause: distutils was removed in Python 3.12; the setup.py install path uses pkg_resources which transitively pulls it in. (See issue #1444.)
Solution:
pip install --user setuptoolsto bring back the shim.- Reinstall:
pip install --user --upgrade thefuck.
Error: pip older than 6.0 not supported
Cause: setup.py refuses pip versions below 6.0.
Solution:
pip install -U pip
pip install --user thefuck
Error: zsh: command not found: thefuck (Termux / minimal Linux)
Cause: Termux installs Python scripts under ~/.local/bin but the shell's PATH may not include it. Reported in issue #650 and #1369.
Solution: Add export PATH="$PREFIX/bin:$HOME/.local/bin:$PATH" to ~/.zshrc (Termux uses $PREFIX for the package root) and source it.
Error: PowerShell profile errors after adding the alias
Cause: Some PowerShell profiles run thefuck --alias synchronously on every shell start, which slows startup. Issue #1380.
Solution: Wrap the alias init in a try/catch and consider caching the alias output:
try {
iex "$(thefuck --alias)"
} catch {
Write-Warning "thefuck alias could not be loaded: $_"
}
Warning: Config path ~/.thefuck is deprecated. Please move to ~/.config/thefuck
Cause: Older releases used ~/.thefuck/. The current release follows the XDG spec.
Solution:
mkdir -p ~/.config
mv ~/.thefuck ~/.config/thefuck
Error: WSL / Cygwin shell detection lands on Generic instead of Bash
Cause: The Fuck walks the parent process tree to detect the shell. In nested terminals (e.g., tmux inside WSL inside Windows Terminal) the parent name may be unrecognised.
Solution: Force it via env var: export TF_SHELL=bash (or zsh/fish/tcsh/powershell).
Error: Shell logger supports only Linux and macOS
Cause: --shell-logger uses pty.fork() and termios; that path is Linux/macOS only.
Solution: Drop the flag on Windows; instant mode is also Linux/macOS only.
Error: terminal misbehaves after eval $(thefuck --alias) on Zsh
Cause: The alias function exports PYTHONIOENCODING, manipulates fc -ln, and inserts a print -s for history rewriting. If a parent script captures stderr, the colored confirm prompt can leak. Reported in issue #1476.
Solution: Make sure you eval the alias output from an interactive shell only — wrap the line:
[[ $- == *i* ]] && eval $(thefuck --alias)
3. Configuration
The Fuck reads settings from three places, applied in this order (later sources win):
~/.config/thefuck/settings.py— a Python file auto-created on first run.THEFUCK_*environment variables.- CLI flags (
-y,-d,-r).
The file is plain Python — assign module-level names. The Fuck imports the file via importlib.util and reads only the keys it knows about.
A minimal settings.py
# ~/.config/thefuck/settings.py
# Only run these rules (default: all)
rules = ['git_push', 'git_pull', 'cd_correction', 'sudo']
# Or: keep defaults but disable specific noisy rules
exclude_rules = ['rm_root', 'sudo']
# Skip the y/n prompt — execute the top suggestion immediately
require_confirmation = False
# Push specific rules to higher priority (lower number = earlier)
priority = {'git_push': 100, 'no_command': 9999}
# Accept up to 5 close-match candidates per rule (default 3)
num_close_matches = 5
Common option combinations
| Goal | Setting |
|---|---|
| Auto-run the first match without confirming | require_confirmation = False |
Re-run fuck if the first fix also fails |
repeat = True |
| Cap how far back to look in history | history_limit = 2000 |
| Disable colors (CI / non-tty) | no_colors = True |
| Disable history rewriting | alter_history = False |
| Whitelist commands that are allowed to take longer | slow_commands = ['gradle', 'mvn', './gradlew', 'vagrant'] |
| Stop searching specific PATH dirs (e.g., a slow NFS mount) | excluded_search_path_prefixes = ['/mnt/slow'] |
| Override the wait timeout for re-running the failed command | wait_command = 5 (seconds) |
| Override the wait timeout for "slow commands" | wait_slow_command = 30 |
Inject env vars into the rerun (default {LC_ALL=C, LANG=C, GIT_TRACE=1}) |
env = {'LC_ALL': 'C'} |
Note
Most settings are also exposed as
THEFUCK_*environment variables so you can tune behavior without editing the file. See 9. Reference → Environment variables for the full list.
Where settings come from at runtime
DEFAULT_SETTINGS → settings.py → THEFUCK_* env vars → CLI flags
If you ever want a fresh settings file, delete it and run fuck again — ~/.config/thefuck/settings.py is regenerated with the default values commented out.
Tip
For deeper architectural detail, the rule plugin protocol, the full settings dictionary, the env-var-to-setting map, and the alias function source code, see the Admin Guide → Configuration Reference and Developer Internals.
4. Core Usage
The fuck alias vs. the thefuck binary
| Command | What it does |
|---|---|
fuck |
The shell function/alias the install instructions add. It captures $PYTHONIOENCODING, exports the recent shell history, calls thefuck, then evals its stdout. This is what you should type. |
thefuck |
The Python entrypoint the alias wraps. You normally do not run it directly — its argument parsing is designed for the alias environment. |
thefuck_firstuse (Windows) / fuck (Linux/macOS, when alias not configured) |
The "not configured" splash. Prints the how-to-configure-alias message; second run within 60 seconds writes the alias line into your shell config automatically. |
CLI flag reference
The Fuck accepts these options (all surface through both the alias and the raw thefuck binary):
| Flag | Type | Purpose |
|---|---|---|
-v, --version |
switch | Print The Fuck X.Y using Python A.B.C and {shell name version} and exit. |
-a [NAME], --alias [NAME] |
optional value | Print the shell alias for the current shell. Optional argument lets you rename it (e.g., --alias FUCK). |
-l PATH, --shell-logger PATH |
value | Spawn a PTY-wrapped subshell that mirrors all output to PATH. Used internally by experimental instant mode. Linux/macOS only. |
--enable-experimental-instant-mode |
switch | Asks the alias to use instant mode (caches command output via shell logger so re-running the failed command is not necessary). Requires the script binary and Python 3. |
-h, --help |
switch | Print usage. |
-d, --debug |
switch | Verbose tracing — every rule import time, every match attempt, the full settings dump, and timing for Total, Read output, etc. |
--force-command CMD |
value | Hidden flag used internally by --repeat to retry a specific command without consulting history. |
command ... |
positional | The command to fix. Normally provided automatically by the alias from fc -ln -10 (or PowerShell Get-History -Count 1). |
-y, --yes, --yeah, --hard |
switch | Mutually exclusive with -r. Skip the up/down/enter prompt — run the top suggestion immediately. |
-r, --repeat |
switch | Mutually exclusive with -y. If the corrected command also fails, run fuck again on its output. |
-y and -r cannot be combined — the parser puts them in a mutually_exclusive_group because chaining them is too dangerous (you could ladder destructive corrections without ever seeing a prompt).
The interactive prompt
When more than one rule matches, you see something like:
git push origin master --force-with-lease (+side effect) [enter/↑/↓/ctrl+c]
| Key | Action |
|---|---|
| Enter / Return | Run the highlighted suggestion. |
↑ or k (qwerty) or e (colemak) or Ctrl+N |
Previous suggestion. |
↓ or j (qwerty) or n (colemak) or Ctrl+P |
Next suggestion. |
Ctrl+C or q |
Abort — print Aborted and do nothing. |
Note
Suggestions appear sorted by rule priority (lower number = higher priority — the prompt's first option is the rule with the smallest priority). You can re-rank rules in
settings.pyvia theprioritydict.
The (+side effect) suffix marks suggestions whose rule defined a side_effect callable. Side-effect-bearing rules in the catalog include dirty_untar, dirty_unzip, and ssh_known_hosts (which removes the conflicting host-key line from ~/.ssh/known_hosts).
How history rewriting works
When you accept a fix, The Fuck writes the corrected command to your shell history so the up-arrow recall is the right command. The implementation differs per shell:
- Bash:
history -s $TF_CMD - Zsh:
print -s $TF_CMD - Fish: deletes the wrong command (
builtin history delete --exact --case-sensitive) and appends the fix to~/.config/fish/fish_history - Tcsh: appends to
~/.history - PowerShell / Generic: no history mutation (PowerShell's history API is per-session)
Disable globally with alter_history = False in settings.py or THEFUCK_ALTER_HISTORY=false.
Where The Fuck runs the failing command
By default, when the alias hands the script to thefuck, the engine re-runs the command in a subprocess with LC_ALL=C LANG=C GIT_TRACE=1 to capture deterministic output, then feeds that output into the rule pipeline. There are three modes:
- Rerun mode (default). The current command is re-executed. This is why corrections of long commands feel slow —
wait_command(default 3 seconds) caps how long it waits, andslow_commandslikelein,react-native,gradle,./gradlew,vagrantget the longerwait_slow_command(default 15 seconds). - Instant mode (
THEFUCK_INSTANT_MODE=trueor--enable-experimental-instant-mode). The alias replaces itself with a shell logger that records every command's output to a temp file. The Fuck then reads from that log instead of re-running. Only Bash and Zsh on Linux/macOS, and you need thescript(1)binary. - Shell logger mode (auto). If a Unix socket is exposed at
$SHELL_LOGGER_SOCKET, The Fuck talks to it and asks for the last 5 commands' output. This is for integrations that record their own shell sessions.
Warning
Instant mode is experimental, sets
THEFUCK_INSTANT_MODE=True, modifiesPS1to embed an invisible "user command mark", and replaces your shell with ascriptrecording. IfPS1is later overwritten by another tool (e.g.,oh-my-zshtheme), instant mode silently falls back to rerun mode and prints a warning to stderr.
Sudo handling
Many rules are wrapped with the @sudo_support decorator. If your script starts with sudo , the wrapper strips it before matching, runs the rule, and re-adds sudo to the suggestion. So sudo apt-get serch foo matches the same apt_get_search rule as apt-get serch foo.
A separate sudo rule fires when the output contains any of ~25 phrases meaning "you need root" — permission denied, must be root, requires superuser, you don't have access to the history db, EACCES, EdsPermissionError, etc. — and prepends sudo (or sudo sh -c "..." if the script contains && or >).
The opposite rule unsudo fires on you cannot perform this operation as root — it strips a leading sudo.
5. Rule Catalog
Rules are Python modules in thefuck/rules/. Each defines match(command) and get_new_command(command). The bundled catalog ships 160+ rules organised below by ecosystem. Rules apply automatically — there is nothing to enable per-rule unless you opted into a restricted rules = [...] list.
Generic / shell built-ins
| Rule | Fires on | Suggests |
|---|---|---|
cd_correction |
cd typo/path with "no such file or directory" / "can't cd to" |
Spell-checks each path component against actual sub-directories |
cd_mkdir |
cd missing/dir (any error) |
mkdir -p missing/dir && cd missing/dir |
cd_parent |
cd.. (typo, no space) |
cd .. |
cd_cs |
cs path (one-key typo for cd) |
cd path |
cat_dir |
cat /some/dir |
ls /some/dir |
chmod_x |
./script.sh with "permission denied" |
chmod +x script.sh && ./script.sh |
cp_create_destination |
cp src dest/ when dest/ does not exist |
mkdir -p dest && cp src dest/ |
cp_omitting_directory |
cp src dest complaining "is a directory" |
cp -a src dest |
dry |
Doubled command (e.g., git git status) |
Drop the duplicate (priority 900 — fires before cd cd foo) |
dirty_untar |
tar -xf archive.tar.gz extracting into the current dir |
mkdir -p archive && tar -xf archive.tar.gz -C archive. Side effect: clean up loose files. |
dirty_unzip |
unzip archive.zip with multiple top-level files |
unzip archive.zip -d archive. Side effect: delete loose files. |
fix_alt_space |
non-breaking space in the command (Alt+Space typo) | Replace with regular space |
fix_file |
Last command output names a file:line (Python, bash, JS, Go, Rust, etc.) | $EDITOR file +line && original_command (requires $EDITOR set; supports per-language patterns) |
has_exists_script |
script_in_cwd returns "command not found" |
./script_in_cwd |
history |
Anything close-matchable to a previous command | The closest historic command (priority 9999 — last-resort) |
ls_all |
ls returning empty |
ls -A |
ls_lah |
Any ls without flags |
ls -lah |
man |
man 3 foo not found |
Tries section 2, then foo --help |
man_no_space |
manfoo (missing space) |
man foo |
mkdir_p |
mkdir a/b/c with "no such file or directory" |
mkdir -p a/b/c |
missing_space_before_subcommand |
gitstatus (run-on command) |
git status |
no_command |
First word not on PATH and resembles something on PATH |
Closest match from history + $PATH |
no_such_file |
mv src dst/missing/path |
mkdir -p dst/missing && mv src dst/missing/path |
open |
open github.com or "file does not exist" |
open http://github.com, touch file && open file, or mkdir file && open file |
path_from_history |
"no such file or directory" with a path | Replace with a similar path that appears in shell history (priority 800 — fires before generic rules) |
quotation_marks |
Mixed ' and " in script |
Normalise to " |
remove_shell_prompt_literal |
Script starts with $ (copy-paste from docs) |
Drop the $ prefix |
remove_trailing_cedilla |
Script ends with ç |
Drop the trailing character |
rm_dir |
rm dir failing "is a directory" |
rm -rf dir (or rm -r for hdfs) |
rm_root |
rm / without --no-preserve-root |
rm / --no-preserve-root (disabled by default — opt-in via rules) |
sl_ls |
Just sl not found |
ls |
sudo |
Any "permission denied" / "must be root" / "EACCES" message | sudo <script> (or sudo sh -c "..." for compound commands) |
sudo_command_from_user_path |
sudo cmd with "command not found" but cmd exists in user PATH |
sudo env "PATH=$PATH" cmd |
unsudo |
"you cannot perform this operation as root" | Strip leading sudo |
switch_lang |
Garbled command typed in wrong keyboard layout (Russian, Ukrainian, Persian, Hebrew, Greek, Korean) | Re-encode to a Latin layout |
touch |
touch path/missing/file |
mkdir -p path/missing && touch path/missing/file |
long_form_help |
Output mentions --help or Run 'foo --help' for details |
Replace -h with --help or run the suggested command (priority 5000) |
wrong_hyphen_before_subcommand |
tool-sub (hyphen instead of space) |
tool sub (priority 4500, no output required) |
Git
| Rule | Fires on | Suggests |
|---|---|---|
git_add |
git checkout/diff/etc. file with "did not match any file(s)" — and the file actually exists |
git add -- file && original |
git_add_force |
git add with "Use -f if you really want to add them" |
git add --force |
git_bisect_usage |
git bisect typo with usage banner |
Closest of start, bad, good, skip, next, reset, visualize, replay, log, run, terms |
git_branch_0flag |
git branch 0d typo-branch (zero typed for hyphen) |
Replace 0 with - (e.g., -d); handles "branch already exists" by adding git branch -D |
git_branch_delete |
git branch -d with "If you are sure you want to delete it" |
git branch -D |
git_branch_delete_checked_out |
git branch -d/-D feature failing "Cannot delete branch checked out at" |
git checkout master && git branch -D feature |
git_branch_exists |
git branch newbranch with "already exists" |
Five alternatives: branch -d && branch, branch -d && checkout -b, branch -D && branch, branch -D && checkout -b, plain git checkout newbranch |
git_branch_list |
git branch list (instead of plain git branch) |
git branch --delete list && git branch |
git_checkout |
"did not match any file(s)" on checkout (no "git add?" hint) | Closest existing branch, or git branch X && git checkout X, or git checkout -b |
git_clone_git_clone |
git clone git clone URL (typo) |
Drop the duplicate git clone |
git_clone_missing |
A bare git URL pasted without git clone |
git clone URL |
git_commit_add |
git commit with "no changes added" |
git commit -a and git commit -p |
git_commit_amend |
Any git commit |
git commit --amend |
git_commit_reset |
Any git commit |
git reset HEAD~ |
git_diff_no_index |
git diff fileA fileB (two files outside repo) |
git diff --no-index fileA fileB |
git_diff_staged |
git diff (without --staged) |
git diff --staged |
git_fix_stash |
git stash typo with usage banner |
Closest of apply, branch, clear, drop, list, pop, save, show; falls back to git stash save typo |
git_flag_after_filename |
"fatal: bad flag X used after filename" or "option must come before non-option args" | Reorder flags before filename |
git_help_aliased |
git help X where X is "aliased to ..." |
git help <real-command> |
git_hook_bypass |
git am/commit/push ... |
Add --no-verify (priority 1100; no output required — opt-in by users with strict pre-commit setups) |
git_lfs_mistype |
git lfs typo with "Did you mean this?" |
Closest LFS subcommand |
git_main_master |
Output mentions 'master' or 'main' |
Swap one for the other (priority 1200) |
git_merge |
git merge X with "not something we can merge" + "Did you mean this?" |
The suggested remote branch |
git_merge_unrelated |
git merge X with "refusing to merge unrelated histories" |
Append --allow-unrelated-histories |
git_not_command |
git typo with "is not a git command. See 'git --help'" + similar list |
Closest similar git command |
git_pull |
git pull with "set-upstream" hint |
Run the suggested set-upstream command, then re-pull |
git_pull_clone |
git pull with "Not a git repository" + "Stopping at filesystem boundary" |
git clone |
git_pull_uncommitted_changes |
git pull with "unstaged changes" / "uncommitted changes" |
git stash && git pull && git stash pop |
git_push |
git push with "git push --set-upstream" message |
The exact --set-upstream line, preserving any extra args |
git_push_different_branch_names |
git push with "upstream branch does not match" |
The recommended git push <remote> <branch> |
git_push_force |
git push rejected because branch is behind |
git push --force-with-lease (disabled by default — opt-in) |
git_push_pull |
git push rejected because remote is ahead |
git pull && git push |
git_push_without_commits |
"src refspec X does not match any" | git commit -m "Initial commit" && git push |
git_rebase_merge_dir |
"rebase-merge directory" already exists | git rebase --continue/--abort/--skip |
git_rebase_no_changes |
git rebase --continue with "No changes" |
git rebase --skip |
git_remote_delete |
git remote delete name |
git remote remove name |
git_remote_seturl_add |
git remote set-url name URL with "No such remote" |
git remote add name URL |
git_rm_local_modifications |
git rm file with "local modifications" |
git rm --cached file and git rm -f file |
git_rm_recursive |
git rm dir with "without -r" |
git rm -r dir |
git_rm_staged |
git rm file with "changes staged in the index" |
git rm --cached file and git rm -f file |
git_stash |
Output contains "or stash them" | git stash && original-command |
git_stash_pop |
git stash pop with overwrite warning (priority 900) |
git add --update && git stash pop && git reset . |
git_tag_force |
git tag X with "already exists" |
git tag --force X |
git_two_dashes |
"did you mean --option" |
Replace -option with --option |
scm_correction |
git ... in an hg repo or vice versa |
Swap to the right SCM |
Note
All Git rules use the
@git_supportdecorator. It also handles GitHub'shubwrapper (alias git=hub) and resolves git aliases by parsingtrace: alias expansion: ...lines from output (set via the defaultenv = {'GIT_TRACE': '1'}).
Docker, Kubernetes-adjacent, and registries
| Rule | Fires on | Suggests |
|---|---|---|
docker_not_command |
"is not a docker command" or Usage: docker banner |
Closest docker (or management) subcommand |
docker_image_being_used_by_container |
docker rmi with "image is being used by running container" |
docker container rm -f <id> && original |
docker_login |
"access denied" + "may require 'docker login'" | docker login && original |
Package managers
| Rule | Fires on | Suggests |
|---|---|---|
apt_get |
cmd: not found / not installed and the system has CommandNotFound |
sudo apt-get install <pkg> && original |
apt_get_search |
apt-get search foo |
apt-cache search foo |
apt_invalid_operation |
apt/apt-get/apt-cache with "Invalid operation" |
Closest valid op (uninstall → remove shortcut) |
apt_list_upgradable |
apt output suggests apt list --upgradable |
Run apt list --upgradable |
apt_upgrade |
After listing upgradable packages | apt upgrade |
brew_install |
brew install foo with "No available formula. Did you mean ..." |
brew install <suggestion> for each candidate |
brew_link |
brew ln/link with "brew link --overwrite --dry-run" hint |
Insert --overwrite --dry-run |
brew_reinstall |
brew install foo already installed and up-to-date |
brew reinstall foo |
brew_uninstall |
brew uninstall/rm/remove with "brew uninstall --force" hint |
Add --force |
brew_unknown_command |
brew typo with "Unknown command" |
Closest from Library/Homebrew/cmd and tap-specific commands |
brew_update_formula |
brew update foo (formula vs. brew itself) |
brew upgrade foo |
brew_cask_dependency |
brew install requiring cask dependencies |
brew cask install dep1 && brew cask install dep2 && original |
cargo |
Bare cargo (no subcommand) |
cargo build |
cargo_no_command |
cargo typo with "no such subcommand" + "Did you mean" |
The suggested cargo subcommand |
choco_install |
choco install foo with "Installing the following packages" |
choco install foo.install |
composer_not_command |
composer typo with "Did you mean this?" |
Suggested command, or install → require |
conda_mistype |
conda typo with "Did you mean 'conda ...'" |
The suggested conda command |
dnf_no_such_command |
dnf typo with "No such command" |
Closest dnf operation |
gem_unknown_command |
gem typo with "Unknown command" |
Closest from gem help commands |
pip_install |
pip install foo with "Permission denied" |
First try --user, then fall back to sudo |
pip_unknown_command |
pip typo with "unknown command, maybe you meant" |
The suggested pip command |
npm_missing_script |
npm run foo with "missing script: foo" |
Closest from npm run-script listing |
npm_run_script |
npm foo where foo is in package.json scripts |
npm run-script foo |
npm_wrong_command |
npm typo with "where <command> is one of" |
Closest npm command |
pacman |
"not found" with pkgfile available |
pacman/yay/pikaur/yaourt -S <package> && original |
pacman_invalid_option |
pacman complaining "invalid option '-x'" with a lowercase short flag |
Uppercase that flag |
pacman_not_found |
pacman/yay/pikaur/yaourt -S with "target not found" |
Replace package name with pkgfile lookup |
yum_invalid_operation |
yum typo with "No such command" |
Closest yum op (uninstall → remove) |
yarn_alias |
yarn typo with "Did you mean" |
The suggested yarn command |
yarn_command_not_found |
yarn typo with "Command not found" |
Closest from yarn --help (with require→add map) |
yarn_command_replaced |
yarn output with "Run 'X' instead" |
Run X |
yarn_help |
yarn help cmd with "for documentation about this command" |
Open the doc URL in default browser |
nixos_cmd_not_found |
"command-not-found" hint with a nix-env -iA pkg line |
nix-env -iA pkg && original |
omnienv_no_such_command |
pyenv/rbenv/nodenv/goenv with "env: no such command" |
Common typos (list → versions, remove → uninstall) and closest from <env> commands |
python_module_error |
ModuleNotFoundError: No module named 'X' |
pip install X && original |
Cloud / DevOps CLIs
| Rule | Fires on | Suggests |
|---|---|---|
aws_cli |
aws ... with "Invalid choice ... maybe you meant" |
One suggestion per option in the list |
az_cli |
az ... with "is not in the X command group" |
Each "most similar choice" |
heroku_multiple_apps |
Output mentions multiple-environments doc |
Re-run with --app <name> for each app |
heroku_not_command |
"Run heroku _ to run" | The suggested heroku command |
terraform_init |
"this module is not yet installed" / "initialization required" | terraform init && original |
terraform_no_command |
"Terraform has no command named X. Did you mean Y?" | Replace X with Y |
tsuru_login |
"not authenticated" + "session has expired" | tsuru login && original |
tsuru_not_command |
tsuru typo with "Did you mean?" |
Suggested tsuru commands |
vagrant_up |
Output suggests "run vagrant up" |
vagrant up && original (and vagrant up <machine> && original if a machine is implied) |
gradle_no_task |
gradle / gradlew with "Task X is ambiguous/not found" |
Closest from gradle tasks |
gradle_wrapper |
gradle ... not on PATH but ./gradlew exists |
./gradlew args |
grunt_task_not_found |
grunt task with "Warning: Task X not found" |
Closest from grunt --help |
gulp_not_task |
gulp X with "is not in your gulpfile" |
Closest from gulp --tasks-simple |
lein_not_task |
lein typo with "is not a task. See 'lein help'" |
Closest similar Leiningen task |
mvn_no_command |
mvn with "No goals have been specified" |
mvn clean package and mvn clean install |
mvn_unknown_lifecycle_phase |
mvn typo with "Unknown lifecycle phase" |
Closest from "Available lifecycle phases" |
react_native_command_unrecognized |
react-native typo with "Unrecognized command" |
Closest from react-native --help |
Languages & build tools
| Rule | Fires on | Suggests |
|---|---|---|
ag_literal |
ag pattern with "run ag with -Q" |
ag -Q pattern |
cpp11 |
g++/clang++ with "C++ 2011" / -Wc++11-extensions |
Append -std=c++11 |
go_run |
go run foo (no .go) |
go run foo.go |
go_unknown_command |
go typo with "unknown command" |
Closest from go help |
grep_arguments_order |
grep <file> <pattern> failing |
Move file to the end |
grep_recursive |
grep pattern dir/ failing "is a directory" |
grep -r pattern dir/ |
java |
java foo.java (running compiled code, suffix wrong) |
java foo (drops .java) |
javac |
javac foo (no .java) |
javac foo.java |
php_s |
php -s ... (lowercase) |
php -S ... |
python_command |
./script.py with "Permission denied" / "command not found" |
python script.py |
python_execute |
python foo (no .py) |
python foo.py |
test.py (file test.py.py) |
test.py not found |
pytest (priority 900 — beats python_command) |
prove_recursively |
prove dir/ with "NOTESTS" |
prove -r dir/ |
Networking, SSH, and system utilities
| Rule | Fires on | Suggests |
|---|---|---|
ifconfig_device_not_found |
ifconfig with "Device not found" |
Closest from ifconfig -a |
ssh_known_hosts |
ssh/scp with "REMOTE HOST IDENTIFICATION HAS CHANGED!" / DNS-spoofing warnings |
Re-run the command. Side effect: remove the offending line(s) from the named known_hosts file. |
port_already_in_use |
EADDRINUSE / "Address already in use" patterns |
kill <pid> && original (uses lsof -i :PORT) |
tmux |
tmux typo with "ambiguous command, could be:" |
One per suggestion |
whois |
whois URL or whois sub.example.com |
Strip path / drop subdomain |
hostscli |
hostscli ... with "No such command" / WebsiteImportError |
Closest of block, unblock, websites, block_all, unblock_all |
systemctl |
systemctl typo with mis-ordered args |
Swap last two args |
unknown_command |
"X: Unknown command. Did you mean Y?" | The suggested command |
workon_doesnt_exists |
workon env for an env that doesn't exist |
Closest from ~/.virtualenvs/, plus mkvirtualenv env |
mercurial |
hg typo with "did you mean one of" / "is ambiguous" |
Closest hg command |
fab_command_not_found |
fab task with "Warning: Command(s) not found" |
Closest from "Available commands:" |
sed_unterminated_s |
sed "s/foo/bar" (missing trailing slash) |
Add the closing / |
Mobile development
| Rule | Fires on | Suggests |
|---|---|---|
adb_unknown_command |
adb typo with "Android Debug Bridge version" banner |
Closest of 35+ adb commands |
Web / scripting frameworks
| Rule | Fires on | Suggests |
|---|---|---|
django_south_ghost |
manage.py migrate with "or pass --delete-ghost-migrations" |
Append --delete-ghost-migrations |
django_south_merge |
manage.py migrate with "--merge: will just attempt the migration" |
Append --merge |
rails_migrations_pending |
"Migrations are pending. To resolve this issue, run: ..." | Run the suggested rake/rails command, then re-run |
ln_no_hard_link |
ln dir target with "hard link not allowed for directory" |
ln -s dir target |
ln_s_order |
ln -s target source (wrong order, "File exists") |
Swap positional args |
Custom & history-based fallback rules (lowest priority — last-resort)
| Rule | Priority | Notes |
|---|---|---|
path_from_history |
800 | Replaces a missing path with one that appears in your shell history |
cd_cs |
900 | Single-key cs typo for cd |
git_stash_pop |
900 | Comes before generic stash rules |
dry |
900 | Doubled command (git git status → git status) |
test.py |
900 | test.py not found → pytest |
git_hook_bypass |
1100 | Add --no-verify to am/commit/push |
git_main_master |
1200 | Swap master ↔ main |
man_no_space |
2000 | manfoo → man foo |
no_command |
3000 | Closest executable for typo'd commands |
missing_space_before_subcommand |
4000 | gitstatus → git status |
wrong_hyphen_before_subcommand |
4500 | git-status → git status |
long_form_help |
5000 | cmd -h → cmd --help (or what the help banner suggests) |
history |
9999 | Closest match from valid shell history (very last resort) |
Tip
The full list above is the bundled catalog. To see what is actually loaded on your system at runtime, run
fuck -d(debug). Each rule's import is logged with timing. Rules that fail to import (missing optional dependency, syntax error in a user rule) are reported as[WARN] Rule X failed to load:followed by the traceback — they are skipped, not fatal.
Authoring your own rule
Drop a ~/.config/thefuck/rules/foo.py with two functions:
# ~/.config/thefuck/rules/uppercase.py
def match(command):
return command.script.startswith('PYTHON ')
def get_new_command(command):
return command.script.replace('PYTHON', 'python', 1)
# Optional metadata:
# enabled_by_default = True
# priority = 1000
# requires_output = True # if False, runs even when there is no output
# def side_effect(old_cmd, new_command_str): ...
The Fuck imports the file with importlib.util.spec_from_file_location, so the only requirement is that match and get_new_command are top-level callables. If get_new_command returns a list instead of a string, the prompt offers each entry as a separate suggestion (priorities are scaled by their order).
For deeper details on the Rule lifecycle, the Command object, the @for_app / @sudo_support / @git_support decorators, side effects, the @cache decorator, and the third-party thefuck_contrib_* discovery mechanism, see the Admin Guide → Developer Internals.
6. Common Tasks
Recipe: Auto-confirm — execute the top suggestion immediately
fuck -y
git push --set-upstream origin master
[main 39e5b1a] ...
Works once. To make it the default everywhere, add require_confirmation = False to ~/.config/thefuck/settings.py or export THEFUCK_REQUIRE_CONFIRMATION=false.
Warning
Auto-confirm is dangerous in scripts that produce ambiguous matches. The Fuck always picks the rule with the smallest priority — if two rules match equally, the alphabetic tiebreaker is the rule filename. For destructive corrections (rm, force-push, ssh known_hosts cleanup), keep the prompt on.
Recipe: Retry until something works
fuck -r
If the corrected command itself fails, The Fuck wraps the suggestion in … || fuck --repeat --force-command "<orig>", so it loops until either a fix succeeds or every rule is exhausted.
You cannot combine -y and -r (the parser refuses).
Recipe: Rename the alias
If fuck is too profane for your workplace:
# In your shell rc:
eval $(thefuck --alias FUCK)
# or:
eval $(thefuck --alias please)
# or even:
eval $(thefuck --alias damnit)
The Fuck's internal TF_ALIAS env var follows the rename, and the "no fucks given" error message becomes "Nothing found" if the alias is anything other than fuck.
Recipe: Disable noisy rules
If sudo keeps suggesting itself for non-sudo problems:
# settings.py
exclude_rules = ['sudo', 'no_command']
Or per-environment:
export THEFUCK_EXCLUDE_RULES="sudo:no_command"
Recipe: Whitelist only a small subset
# settings.py
rules = ['git_push', 'git_pull', 'cd_correction', 'cd_mkdir', 'mkdir_p']
Combine with DEFAULT_RULES to keep all defaults but pin extras:
export THEFUCK_RULES="DEFAULT_RULES:rm_root:git_push_force"
Recipe: Boost a rule's priority so it always wins
# settings.py — smaller numbers fire first
priority = {
'git_push': 100, # always before all other git push fixes
'history': 9999, # keep history as last-resort
'no_command': 9000,
}
Or via env:
export THEFUCK_PRIORITY="git_push=100:history=9999"
Recipe: Tame slow commands (avoid the 3-second cutoff)
The default rerun timeout is 3 seconds; any command in slow_commands gets wait_slow_command (15s).
# settings.py
slow_commands = ['gradle', './gradlew', 'mvn', 'lein', 'react-native', 'vagrant', 'docker-compose']
wait_slow_command = 30
Or:
export THEFUCK_SLOW_COMMANDS="gradle:./gradlew:mvn:lein:react-native:vagrant"
export THEFUCK_WAIT_SLOW_COMMAND=30
Recipe: Inject env vars during the rerun
The default is {LC_ALL: C, LANG: C, GIT_TRACE: 1} — LC_ALL=C makes errors deterministic across locales, and GIT_TRACE=1 lets git rules see alias expansions. To add (not replace) entries:
# settings.py
env = {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1', 'NO_COLOR': '1'}
Recipe: Use The Fuck inside Docker / inside a CI pipeline
CI typically lacks a tty, so the prompt would hang. Use:
export THEFUCK_REQUIRE_CONFIRMATION=false
export THEFUCK_NO_COLORS=true
fuck
Or run the binary directly bypassing the alias:
thefuck --yes <last command line here>
Recipe: Try experimental instant mode (Bash/Zsh + Linux/macOS)
Instant mode skips the rerun by recording every command's output to a temp log:
# In your shell rc, replace the eval line with:
eval "$(thefuck --alias --enable-experimental-instant-mode)"
Requirements:
- Python 3 (instant mode warns and bails on Python 2).
- The
script(1)binary onPATH. - A
PS1that does not get rewritten after The Fuck initialised it (oh-my-zsh themes that re-setPS1on every prompt break instant mode silently). - Bash or Zsh — Fish/Tcsh/PowerShell fall back to rerun mode with a warning.
Recipe: Disable history rewriting
Some setups don't want their history mutated:
# settings.py
alter_history = False
The corrected command still runs; it just won't appear via up-arrow.
Recipe: Migrate from ~/.thefuck/ to ~/.config/thefuck/
mkdir -p ~/.config
mv ~/.thefuck ~/.config/thefuck
The Fuck will use the legacy path with a Config path … is deprecated warning if both exist; once migrated, the warning disappears.
Recipe: Add a third-party rule pack
Any pip package whose name starts with thefuck_contrib_ and that exposes a rules/ sub-folder is discovered automatically:
pip install --user thefuck_contrib_my_company
The corrector's get_rules_import_paths() walks every sys.path entry and globs thefuck_contrib_*/rules/.
Recipe: Force a specific shell
If shell auto-detection fails (nested terminals, exotic terminals, embedded PTYs):
export TF_SHELL=zsh # one of: bash, zsh, fish, csh, tcsh, powershell, pwsh
Recipe: Fix a file at a specific line (the fix_file rule)
$ python my_script.py
File "my_script.py", line 42
bad_syntax(
^
SyntaxError: unexpected EOF
$ fuck
vim my_script.py +42 && python my_script.py [enter/↑/↓/ctrl+c]
The fix_file rule recognises Python, Bash, JS/Node, Cargo/Rust, Go, GHC/Haskell, Make, Ruby, Zsh, Lua, Awk, Git config, LLC, Perl, and Fish error formats. Requires $EDITOR to be set; respects fixlinecmd and fixcolcmd settings if you want a non-default invocation.
7. Troubleshooting
A diagnostic decision tree:
- Does
thefuck --versionprint? → install OK. - Does
fuck(with the alias) print "no fucks given"? → alias OK, no rule matched (or every rule that matched produced an empty suggestion). - Does
fuck -dshowImporting rule X failed? → bad rule file, fix that first. - Is the wrong suggestion winning? → adjust
priorityinsettings.py. - Is the corrected command itself failing? → use
fuck -r.
Error: No fucks given
Cause: No rule's match() returned True. Either nothing in the catalog handles your command, or your output cache is empty (instant mode misconfigured).
Solution:
- Run
fuck -dto see which rules tried and what they saw. - Confirm the alias actually exported
TF_HISTORY— the alias should set it to$(fc -ln -10). - If you customised
rules = [...], add the rule that should match.
Prevention: Author a custom rule (see Admin Guide → Developer Internals) for command patterns you hit often.
Error: Nothing found
Cause: Same as above, but you renamed the alias — the friendly "no fucks given" only fires when the alias is literally fuck.
Error: Seems like fuck alias isn't configured!
Cause: You ran fuck but the alias is not exported. The "not-configured" entrypoint took over.
Solution:
- Run the appropriate
eval $(thefuck --alias)for your shell. - Save it to your shell rc and
sourcethe file. - Or run
fucka second time within 60 seconds — The Fuck auto-writes the alias if it can detect your shell config.
Error: Shell logger doesn't support your platform.
Cause: --shell-logger (or instant mode) tried to start on Windows, FreeBSD, or a stripped-down Linux without a $SHELL env var.
Solution: Drop --enable-experimental-instant-mode from your alias line; instant mode is Linux/macOS only.
Error: Output log isn't specified (instant mode)
Cause: Instant mode launched but THEFUCK_OUTPUT_LOG was not exported. The shell logger sub-process did not start cleanly.
Solution:
- Verify the
script(1)binary is onPATH. - Re-source your shell rc.
- If the warning persists, drop instant mode — rerun mode is the safer default.
Error: PS1 doesn't contain user command mark
Cause: Instant mode embeds an invisible marker (U+200B × 10) in PS1. Another tool (oh-my-zsh, starship, powerlevel10k) overwrote PS1 after init.
Solution:
- Make the
eval $(thefuck --alias --enable-experimental-instant-mode)line the last line in your rc. - Or disable instant mode and use the default rerun mode.
Error: Removing possibly out-dated cache
Cause: Switched Python versions; the on-disk shelve cache uses an incompatible dbm format.
Solution: Harmless — The Fuck deletes ~/.cache/thefuck and rebuilds it. Look in XDG_CACHE_HOME (default ~/.cache) if you want to clear it manually.
Error: Can't load settings from file
Cause: ~/.config/thefuck/settings.py raised an exception during import.
Solution:
- Run
fuck -dand read the printed traceback. - Common culprit: typo'd a setting name (e.g.,
excluded_rulesinstead ofexclude_rules). - Worst case, delete the file — The Fuck regenerates a defaults-only template on next run.
Error: 'PATH' environment variable not set / no executables discovered
Cause: Some sandboxed runtimes (Docker --env-clean, systemd unit without Environment=PATH) clear $PATH. The no_command rule and the alias both depend on it.
Solution: Set $PATH in the environment before running the alias.
Error: Can't update history (Fish)
Cause: ~/.config/fish/fish_history is read-only or on a read-only FS.
Solution: chmod u+w ~/.config/fish/fish_history. Or set alter_history = False.
Error: Suggestion includes the wrong sudo behaviour
Cause: The sudo rule's pattern list can match unexpected output (e.g., a docs string containing "permission denied").
Solution: Add sudo to exclude_rules, or boost the priority of the rule you want to win:
# settings.py
priority = {'git_push': 100} # win over sudo
Error: Shell hangs after fuck
Cause: The rerun is waiting on a long-running command (the failed one) to finish. Default timeout is 3 seconds; the slow-command list adds 15.
Solution: Press Ctrl+C — The Fuck kills the process tree. Add the binary to slow_commands so future runs use the longer timeout.
Error: [WARN] Rule X failed to load
Cause: A custom or contrib rule raised an exception at import time.
Solution:
- Run
python -c "import sys; sys.path.insert(0, '~/.config/thefuck/rules'); import X"to surface the import error. - Either fix the file or move it out of
~/.config/thefuck/rules/.
Error: [WARN] Could not determine shell version
Cause: _get_version() shelled out (e.g., bash -c 'echo $BASH_VERSION') and the subprocess errored.
Solution: Cosmetic — fuck --version will just say "Bash" instead of "Bash 5.2". Investigate by running the underlying command yourself.
Error: Mac Termux "termux errors, but thefuck works"
Cause: Reported as issue #1369. Termux's path conventions throw spurious warnings during alias init.
Solution: Cosmetic; ignore or redirect stderr in your rc:
eval "$(thefuck --alias)" 2>/dev/null
Error: Warning: thefuck-fixed
Cause: Tracked in issue #1570 — a third-party command produces an unparseable output that triggers a misclassified rule.
Solution: Run with -d to see which rule matched; add it to exclude_rules.
Error: Use of deprecated / removed pkg_resources
Cause: setup.py uses pkg_resources.get_distribution which is deprecated in newer setuptools (issue #1552).
Solution: Cosmetic during install. Upgrading setuptools to a recent release silences it. Does not break runtime.
Error: libexpat now an explicit install requirement on macOS
Cause: Issue #1560 — Homebrew Python now requires libexpat to be explicitly linked.
Solution: brew install libexpat then reinstall thefuck.
Error: [self-tests] fail against pytest 9
Cause: Issue #1549 — only relevant if you are running pytest against The Fuck's own test suite. Does not affect end-user behaviour.
Solution: Pin pytest<9 in your dev environment.
8. FAQ
Q: Does The Fuck phone home or upload anything?
No. Everything runs locally. The only network calls come from rules that explicitly need them (e.g., apt_get consults the local CommandNotFound database; pacman_not_found calls local pkgfile). Nothing is sent to a remote service.
Q: What gets cached and where?
The Cache class in thefuck/utils.py writes a shelve database under $XDG_CACHE_HOME/thefuck (default ~/.cache/thefuck). It memoises results of expensive commands like brew --prefix, docker --help, gem help commands, etc., keyed by the modification time of the binary so cache invalidates automatically when you upgrade. Delete the directory to reset.
Q: Can I see what The Fuck would do without running anything?
Run thefuck "<the failing command>" directly. It prints the would-be-evaluated script to stdout but does not eval it. The alias is what actually runs eval.
Q: How do I disable confirmation only for non-destructive rules?
Not directly — require_confirmation is global. The workaround is to keep require_confirmation=True and use -y selectively (fuck -y for one-offs).
Q: Does The Fuck work over SSH?
Yes, as long as you install it on the remote machine and add the alias to that machine's shell rc. There is no client/server protocol.
Q: Does The Fuck work in tmux/screen?
Yes. Shell detection walks the parent process tree, so bash inside tmux resolves correctly. If detection lands on Generic, set TF_SHELL explicitly.
Q: Why does git push sometimes get fixed twice in a row?
Because the first fix added --set-upstream (rule git_push) and the second added --force-with-lease (rule git_push_force, which is disabled by default — but if you opted into it via rules = [...], it can chain). Disable one of them or use priority to choose which fires first.
Q: Why does my shell startup feel slower after adding the alias?
The alias itself is just a function definition — its cost is negligible. The first call to fuck is slower because The Fuck imports every rule file in the catalog. To shave time, set rules = [...] in settings.py to whitelist just the rules you actually use.
Q: Why does The Fuck want script(1) for instant mode?
Instant mode replaces your interactive shell with a script -f recording so it can read command output from the log file instead of re-running. script is part of bsdutils on Debian/Ubuntu, util-linux on Arch, and ships with macOS. If script is missing, instant mode silently falls back to rerun mode.
Q: Can I run The Fuck inside a non-interactive script?
Yes, via the raw thefuck binary plus THEFUCK_REQUIRE_CONFIRMATION=false. But you'll lose the alias's history capture, so pass the failing command as positional args:
THEFUCK_REQUIRE_CONFIRMATION=false thefuck git pus origin main
Q: Does The Fuck support PowerShell on Linux?
Yes — both powershell and pwsh map to the Powershell shell class. The alias uses Get-History -Count 1 for history capture. PowerShell does not support history rewriting (alter_history is a no-op there).
Q: Where is the source for the rule plugin protocol?
thefuck/types.py defines the Rule dataclass and the from_path classmethod that loads each rule file. thefuck/corrector.py is the orchestrator that walks rule paths, filters by is_enabled, and yields suggestions in priority order. See the Admin Guide for a deep dive.
Q: Is there a Windows native build?
pip install thefuck works on Windows. The setup ships scripts/fuck.bat and scripts/fuck.ps1. Bash/Zsh/Fish are not supported on plain Windows; use PowerShell or WSL.
Q: How do I share rules across a team?
Two options:
- Publish them as a
thefuck_contrib_yourteampip package — every member's The Fuck auto-discovers it. - Check the rule files into a shared repo and have everyone symlink them into
~/.config/thefuck/rules/.
Q: Can I disable colors permanently?
Yes — no_colors = True in settings.py, or THEFUCK_NO_COLORS=true, or run inside any non-TTY environment (colorama detects this for free).
Q: Why did the alias change my PYTHONIOENCODING?
The alias temporarily exports PYTHONIOENCODING=utf-8 so The Fuck can decode shell output safely on Windows. It saves the original value in TF_PYTHONIOENCODING and restores it after the call. If you see Unicode errors, your shell may be running an outdated alias — re-eval $(thefuck --alias).
Q: How does Git alias expansion work?
Some Git aliases hide the real command (alias.s = status). The Fuck sets GIT_TRACE=1 during the rerun. Git emits trace: alias expansion: s => status to stderr; the @git_support decorator parses that line and rewrites the script before calling the rule's match(). Without GIT_TRACE, Git rules cannot see what the alias resolves to.
Q: Will The Fuck modify my files?
Only via rules that declare a side_effect callable. The bundled side-effect-bearing rules are:
dirty_untar— removes loose extracted files in CWD.dirty_unzip— removes loose extracted files in CWD.ssh_known_hosts— deletes the offending line(s) from the named known_hosts file.
These are marked with (+side effect) in the prompt — accept only if you understand what they do.
Q: I heard the project is unmaintained?
Maintenance has slowed (issues #1466 and #1566 discuss this). The codebase is stable and the rule mechanism is extensible enough that you can ship your own rules locally without waiting on upstream.
9. Reference
Environment variables
| Variable | Maps to setting | Type | Notes |
|---|---|---|---|
THEFUCK_RULES |
rules |
colon-separated | Use DEFAULT_RULES to mean "everything" |
THEFUCK_EXCLUDE_RULES |
exclude_rules |
colon-separated | Disabled rules are skipped at import time |
THEFUCK_WAIT_COMMAND |
wait_command |
int seconds | Default 3 |
THEFUCK_REQUIRE_CONFIRMATION |
require_confirmation |
true/false |
Default true |
THEFUCK_NO_COLORS |
no_colors |
true/false |
Default false |
THEFUCK_DEBUG |
debug |
true/false |
Default false |
THEFUCK_PRIORITY |
priority |
colon-separated key=int pairs |
e.g., git_push=100:history=9999 |
THEFUCK_HISTORY_LIMIT |
history_limit |
int | Default unlimited |
THEFUCK_ALTER_HISTORY |
alter_history |
true/false |
Default true |
THEFUCK_WAIT_SLOW_COMMAND |
wait_slow_command |
int seconds | Default 15 |
THEFUCK_SLOW_COMMANDS |
slow_commands |
colon-separated | Default lein:react-native:gradle:./gradlew:vagrant |
THEFUCK_REPEAT |
repeat |
true/false |
Default false |
THEFUCK_INSTANT_MODE |
instant_mode |
true/false |
Default false |
THEFUCK_NUM_CLOSE_MATCHES |
num_close_matches |
int | Default 3 |
THEFUCK_EXCLUDED_SEARCH_PATH_PREFIXES |
excluded_search_path_prefixes |
colon-separated | Skip these PATH dirs when listing executables |
Other env vars used internally
| Variable | Set by | Purpose |
|---|---|---|
TF_ALIAS |
the shell alias | Lets The Fuck recognise its own alias and skip self-corrections |
TF_SHELL |
the shell alias | Forces shell detection bypass |
TF_HISTORY |
the shell alias | Pre-captured fc -ln -10 output passed in |
TF_SHELL_ALIASES |
the Bash/Zsh alias | Pre-captured alias listing for shell-alias resolution |
TF_OVERRIDDEN_ALIASES / THEFUCK_OVERRIDDEN_ALIASES |
user | Comma-separated list of Fish aliases that override built-ins (default: cd, grep, ls, man, open) |
TF_PYTHONIOENCODING |
the shell alias | Saved $PYTHONIOENCODING; restored after the call |
THEFUCK_OUTPUT_LOG |
instant-mode alias | Path to the shell-logger temp file |
XDG_CONFIG_HOME |
OS / user | Base for the settings dir (default ~/.config) |
XDG_CACHE_HOME |
OS / user | Base for the cache dir (default ~/.cache) |
HISTFILE |
shell | Used by Bash/Tcsh/Zsh history reading |
EDITOR |
user | Required for the fix_file rule to fire |
SHELL |
OS | Used by --shell-logger |
PYTHONIOENCODING |
the shell alias | Forced to utf-8 for the duration of the call |
SHELL_LOGGER_SOCKET |
external integration | If set, The Fuck reads command output via this Unix socket instead of rerunning |
Default settings
DEFAULT_SETTINGS = {
'rules': [ALL_ENABLED],
'exclude_rules': [],
'wait_command': 3,
'require_confirmation': True,
'no_colors': False,
'debug': False,
'priority': {},
'history_limit': None,
'alter_history': True,
'wait_slow_command': 15,
'slow_commands': ['lein', 'react-native', 'gradle', './gradlew', 'vagrant'],
'repeat': False,
'instant_mode': False,
'num_close_matches': 3,
'env': {'LC_ALL': 'C', 'LANG': 'C', 'GIT_TRACE': '1'},
'excluded_search_path_prefixes': [],
}
Files & directories
| Path | Purpose |
|---|---|
~/.config/thefuck/settings.py |
User Python settings (auto-generated) |
~/.config/thefuck/rules/ |
User custom rules (.py files) |
~/.thefuck/ |
Legacy path (still honoured with a deprecation warning) |
$XDG_CACHE_HOME/thefuck |
shelve cache for memoised commands |
/tmp/thefuck-script-log-<uuid> |
Instant-mode output log |
/tmp/thefuck.last_not_configured_run_<user> |
Tracker for the auto-configure-on-second-run flow |
Exit codes
| Code | Meaning |
|---|---|
| 0 | A correction was selected and emitted to stdout (the alias evals it). |
| 1 | Nothing matched, or the user pressed Ctrl+C / q. |
| -1 | Python version check failed in setup.py (install-time only). |
Related links
- Source: github.com/nvbn/thefuck
- Issues: github.com/nvbn/thefuck/issues
- Latest release: pypi.org/project/thefuck
- Author: Vladimir Iakovlev (
[email removed]) - License: MIT
For architecture, the rule plugin protocol, internals, and contributing, continue to the Admin Guide.