Fix your last failed shell command.
oops silently watches every command you run. When one fails, type oops and it suggests — or automatically applies — a corrected version. The failed command is never re-executed; oops analyzes the command string, exit code, and context (git state, filesystem, shell history) to produce a fix.
$ gti status
zsh: command not found: gti
$ oops
git status
On branch main...
go install github.com/pxgray/oops/cmd/oops@latestOr clone and build:
git clone https://github.com/pxgray/oops
cd oops
go build -o bin/oops ./cmd/oops # build to ./bin/oops
# or
go install ./cmd/oops # install to $GOPATH/binRequires Go 1.25+. The binary has no runtime dependencies.
After installing the binary, add the hook to your shell's config file. The hook tracks each command passively — it never re-runs anything.
Add to ~/.zshrc:
eval "$(oops --alias zsh)"Add to ~/.bashrc:
eval "$(oops --alias bash)"Add to ~/.config/fish/config.fish:
oops --alias fish | sourceRestart your shell (or source the config file) to activate. You can now type oops after any failed command.
Just type oops after a failed command:
$ cd nonexistent-dir
bash: cd: nonexistent-dir: No such file or directory
$ oops
mkdir -p nonexistent-dir && cd nonexistent-dir
When multiple fixes are possible, an interactive picker appears (requires a TTY). Use arrow keys to select and Enter to confirm. Press q or Ctrl+C to cancel without running anything.
| Situation | Example | Fix |
|---|---|---|
| Typo in command name | gti status |
git status |
| Permission denied | cat /etc/shadow |
sudo cat /etc/shadow |
cd to nonexistent dir |
cd myproject |
mkdir -p myproject && cd myproject |
| Wrong git branch name | git checkout mian |
git checkout main |
| Push without upstream | git push (no upstream) |
git push --set-upstream origin <branch> |
| Nothing staged to commit | git commit -m "..." |
git commit --amend -m "..." |
| Python venv not activated | python script.py (venv present) |
source venv/bin/activate && python script.py |
| Repeated history prefix | docker run ... (partial match) |
best matching prior command |
oops # print help
oops --alias SHELL # print shell integration hook (bash/zsh/fish)
oops run # analyze and fix last command (called by the hook)
oops rules # list all rules and their enabled/disabled status
oops config # show config file path and current settings
Config file: ~/.config/oops/config.toml (created on first use if needed; missing file uses defaults silently).
[core]
timeout = "5s" # max time for rule evaluation
cache_max_age = "1h"
[predictor]
history_limit = 10000 # how many history entries to load
max_suggestions = 5 # max candidates shown in picker
[ui]
interactive = true # show TUI picker when multiple fixes exist
color = true
[rules]
disabled = [] # list rule names to disable, e.g. ["history_repeat"][rules]
disabled = ["history_repeat", "python_venv"]Available rule names: command_not_found, git_branch, git_push_upstream, git_commit_amend, cd_mkdir, python_venv, typo_flag, history_repeat.
Run oops rules to see all rules and their current status.
oops uses a priority-ordered rule pipeline:
- The shell hook captures the command string and exit code after each prompt.
- On
oops, the hook passesOOPS_CMD,OOPS_EXIT,OOPS_CWD, andOOPS_SHELLtooops run. - Rules are evaluated in priority order; each rule inspects the command and optionally performs secondary checks (git state, filesystem presence, etc.).
- Candidates are ranked by confidence. The highest-confidence fix is applied automatically, or presented in the interactive picker.
- The chosen command is written to stdout; the shell hook
evals it. All UI output goes to/dev/tty.
Command typo correction uses a BK-tree with Damerau-Levenshtein distance (transpositions cost 1) combined with Jaro-Winkler fuzzy scoring, so single-character transpositions like gti → git are reliably caught.
task build # build to bin/oops
task test # run all tests
task install # install to $GOPATH/bin
task clean # remove bin/Run a single test:
go test ./internal/rules/ -run TestCdMkdir -v