Reading what your failed command printed sometimes means running it again, and
this release is mostly about that decision being sound. Three independent
reviews took the tool apart; everything below came out of them, out of the
suggestion corpus, or out of sitting down and typing typos into a real shell.
Upgrade if you use the automatic replay — that is, if you have ever seen
"has to run again to be read" and let it go ahead without being asked.
Your previous command runs again less often, and never on a bad proof
bleep skips asking when running your command again cannot have an effect. Four
ways it could reach that conclusion wrongly:
- Exit status 127 meant "nothing happened". It does not.
make,npm run
andsh -call report a child's missing command that way, after the rest of
the work has already happened — so a failed build could be re-run unasked. - An empty
PATHentry means the current directory. Skipping it made a
local./deploylook absent, so it was re-run without asking. bash -cimports exported shell functions, and sources$BASH_ENVbefore
the command it was given. A function that failed ran twice; a command that did
not exist at all still had an effect.- An assignment in front of the command was dropped from the proof.
PATH=/tmp/mine git satushad the dispatcher asking/usr/bin/gitabout
satusand using the answer to authorise re-running a differentgit.
Each of those was reproduced end to end before it was fixed, and each has a test
that fails if it comes back.
This means more prompts than 4.0.2, not fewer. A command with a leading
assignment (LC_ALL=C ls), or given as a path (/bin/ls, ./deploy), now asks
before it is re-run. The alternative was keeping a list of environment variables
believed harmless, and this is the wrong project for a list like that.
Also: xxd is no longer treated as read-only (it takes an output file, and
xxd -r patches one in place), and git commit that failed is no longer
answered with git reset HEAD~ — every failed commit is a commit that has not
happened, so what that offered to throw away was the one before it.
Two ways it could run code as you
- The cache directory fell back to a predictable name in a shared
/tmpwhen
there was no home directory to use, created with whatever the umask allowed.
The compiled rule pack lives there and is executed on the next correction, so
whoever created that directory first ran code as you. It is0700now, and
refused outright if it belongs to somebody else. - The first-run tracker was a predictable
/tmpname opened for writing, so a
symlink planted there was followed.
--debug no longer prints the values of your env setting, which is where
people keep tokens — and the issue template now says to read what you paste.
It is right more often
Four rules were dead against what their tool actually prints — git add,
git stash pp, git rebase with a leftover merge directory, and hostscli —
each for however many releases ago the wording moved. The framework catches what
a rule raises, so a dead rule looks exactly like a rule with nothing to say.
That class now has two gates: every rule is thrown malformed output and held to
not raising, and --doctor reports any that do.
Suggestions that were confidently wrong:
ls-laansweredlsblk, andgitstatusansweredaa-status, withls -la
andgit statussitting one row further down the list.sudo apt-get updteon a machine withoutsudoansweredsu do apt-get updte.systemctl statu sshansweredsysctl statu ssh.ln -s /etc/hostname /tmp/link, where the link already existed, offered a
symlink on top of/etc/hostname.mkdir -p x && rmdir yansweredmkdir -p -p x.diff --colour=alwys a banswereddiff --color a b, silently dropping the
value.
And three new rules that each cover an ecosystem rather than a tool:
argparse_invalid_choice— Python's standard library parser, sopytest,
mypy,pre-commit,toxandcoveragecome with it. Thanks to
@TrixSec (#5).commander_suggestion— commander.js, soprettier,eslint,prisma,
nestandturbocome with it. Thanks to
@TrixSec (#4).git_unknown_subcommand—git remote ad,git worktree lst,
git notes ad. git prints its usage rather than a suggestion for these, and
nothing was reading it.invalid_argument_for_option—ls --sort=nmae, and every other GNU tool
that prints the value it refused and the ones it accepts.
The measured suggestion quality is now 85/85 against The Fuck 3.32's
55/85, on a corpus of real captured output. That number is reproducible for the
first time: it used to consult Debian's command-not-found database, so it was
measuring the machine.
Faster where it matters
An unknown command is the commonest way a command fails, and it scanned every
name on PATH twice. 187ms to 93ms across five typos. A multi-megabyte shell
history is read from the end rather than in full, and a replayed command's output
is capped rather than held whole.
Also
Rules can no longer start a program without a timeout — twenty of them could,
and a rule that never returns is bleep frozen at your prompt. Every GitHub
Action in the release workflow is pinned to a commit. ctrl+c at the prompt no
longer raises, on Windows or on POSIX. ctrl+p and ctrl+n go the way every
shell's history goes.
Full detail: CHANGELOG.md