Zsh History: Fix Lost Commands, Search It, or Replace It (2026)
Why Zsh loses history, the HISTSIZE, SAVEHIST and shared-history settings that fix it, how to search it, and when a replacement such as Atuin or Suvadu is worth it.
When Zsh history lets you down, it is usually one of two different problems. Either the command was never saved (or the file was damaged), or it is saved but you cannot find it quickly. A replacement tool can help with the second problem, but the first is often a few lines of Zsh configuration. This guide shows how to check and fix native Zsh history first, how to search it well, where a flat history file really runs out, and how to install, import into, and roll back a database-backed replacement.
Review note: I build Suvadu, one of the tools discussed here. I reviewed this guide on 19 September 2026 against the Zsh 5.9 manual (zshoptions, zshbuiltins, zshzle) and Suvadu 0.4.1, and updated the Suvadu search and import claims on 21 September 2026 for Suvadu 0.4.2. On 26 September 2026 I re-checked the Suvadu claims for Suvadu 0.5.0 and updated the install and uninstall steps, which that release changes. For Suvadu 0.5.1 I updated the import, search and arrow-key steps, which that release changes. On 20 October 2026 I added Oh My Zsh's defaults, the steps to remove or clear history, a safer fix for a corrupt history file and the quick answers, each tested in Zsh 5.9 on macOS.
What built-in Zsh history already does
Zsh keeps an in-memory history list and, if HISTFILE is set, saves it to a text file such as ~/.zsh_history. A handful of parameters and options control it:
HISTFILEis where history is saved. If it is unset, history lives only in memory and disappears when the shell exits. macOS sets it in/etc/zshrc; on other systems it depends on your distribution or framework.HISTSIZEis how many entries each shell keeps in memory;SAVEHISTis how many are written to the file. macOS defaults are 2,000 and 1,000. Older commands are dropped once these limits are reached, so raise them if you want long history, keepingHISTSIZEat least as large asSAVEHIST.- Oh My Zsh sets its own history defaults in
lib/history.zsh:~/.zsh_historyifHISTFILEis unset,HISTSIZEof at least 50,000 andSAVEHISTof at least 10,000 (it only raises them), and the optionsextended_history,hist_expire_dups_first,hist_ignore_dups,hist_ignore_space,hist_verifyandshare_history. To use an incremental-append option instead, putunsetopt share_historyafter the line that sources Oh My Zsh. INC_APPEND_HISTORYwrites each command to the file as soon as it is entered instead of when the shell exits, so a crashed or killed terminal does not lose its session.INC_APPEND_HISTORY_TIMEwrites after the command finishes, which records durations correctly.SHARE_HISTORYappends commands as they are entered and also imports new commands from other running shells. The manual treats it and the two incremental-append options as alternatives: set one, not all three.EXTENDED_HISTORYsaves each command's start time and duration in the file (: 1758268800:3;make test).HIST_IGNORE_DUPSskips a command identical to the previous one;HIST_IGNORE_ALL_DUPSremoves older duplicates;HIST_IGNORE_SPACEdrops commands typed with a leading space, which is useful for commands you do not want saved.
A reasonable starting point in ~/.zshrc:
HISTFILE=~/.zsh_history
HISTSIZE=100000
SAVEHIST=100000
setopt EXTENDED_HISTORY
setopt SHARE_HISTORY # or INC_APPEND_HISTORY / INC_APPEND_HISTORY_TIME, not together
setopt HIST_IGNORE_DUPS
setopt HIST_IGNORE_SPACE Missing or damaged history vs. hard-to-find history
Work out which problem you have before changing tools:
- Commands never reach the file. Check
echo $HISTFILE $HISTSIZE $SAVEHISTin the affected shell. An unsetHISTFILE, a tinySAVEHIST, or a framework overriding your values after your settings will all lose history. Without an incremental-append or sharing option, a terminal that is killed rather than exited never writes its commands. - Other terminals do not see new commands. That is expected without
SHARE_HISTORY: each shell loads the file at startup. Runfc -RIto read new entries from the file on demand, or enable sharing. - "zsh: corrupt history file". The file contains a damaged or truncated entry, often NUL bytes after a crash or a full disk. Close other shells so they do not rewrite it, back it up, and strip the NUL bytes:
The often-copiedcp ~/.zsh_history ~/.zsh_history.bak LC_ALL=C tr -d '\000' < ~/.zsh_history.bak > ~/.zsh_historystringsfix also silences the error, but it cuts every command containing non-ASCII text (git commit -m "café"becomesgit commit -m "caf). If the error remains, the damage is something else: find the line in an editor. A history replacement does not repair this file for you. - The command is saved but you cannot find it. That is a recall problem. Try the native search tools below before deciding it is a tooling problem.
Search native Zsh history before you replace it
Native Zsh search is more capable than its reputation:
- Ctrl+R runs
history-incremental-search-backwardin the default emacs keymap. Keep pressing it to step to older matches. It is not bound in the vi keymaps, so bind it yourself if you use vi mode.history-incremental-pattern-search-backwardaccepts glob patterns. fc -llists history;fc -l 1lists all of it, and piping togrepgives you every match at once:fc -l 1 | grep docker.history -E(or-ifor ISO format) shows when each command ran, which needsEXTENDED_HISTORYfor commands from earlier sessions.- zsh-history-substring-search makes Up/Down cycle through commands containing what you have typed, similar to the Fish shell.
Remove a command from Zsh history, or clear it
Editing ~/.zsh_history while a shell is open often does nothing: with the default settings, each shell writes its in-memory history back to the file when it exits, putting the line straight back. These steps work with the default settings, INC_APPEND_HISTORY and SHARE_HISTORY alike (tested in Zsh 5.9):
- Close every other Zsh terminal and tab, so none of them writes its copy back later.
- In the one that is left, run
fc -Wto write its history to the file first. - Remove the line, in an editor or with
sed(sed -i '' '/secret-token/d' "$HISTFILE"on macOS,sed -i '/secret-token/d' "$HISTFILE"on Linux). - Run
unset HISTFILE; exitwith a leading space, so this shell saves nothing on the way out and, withHIST_IGNORE_SPACE, the line itself is not saved either.
To clear all of it, do the same with an empty file: close the other terminals, then run : >| "$HISTFILE"; unset HISTFILE; exit. Oh My Zsh's history -c also works there, because Oh My Zsh turns on share_history; with the default settings its method leaves the old history in the file.
Where a history file runs out
With good settings, native history is reliable for many people. A flat file still has limits that configuration cannot fix:
- Little context. Even with
EXTENDED_HISTORY, the file stores a start time, duration, and command. It does not record the directory, the exit status, or which terminal session ran a command, so you cannot ask "what failed in this project yesterday?" - One match at a time. Ctrl+R shows a single match; to see and filter many results you drop to
fc -l | grepor add a tool such as fzf. - Size limits are a trade-off. You can raise
SAVEHISTa lot, but the file is rewritten to trim it and every shell reads it at startup. - No view of AI agents. Coding agents usually run commands in non-interactive shells, which do not write to your history file, so their commands rarely appear there.
Your options in 2026
| Tool | What it is | Best for |
|---|---|---|
| Native Zsh + plugins | The settings above, plus zsh-history-substring-search | Keeping your setup small once recording is fixed |
| fzf | A fuzzy finder bound to Ctrl+R over your existing history | A quick, zero-lock-in search upgrade |
| McFly | Neural-ranked Ctrl+R with a SQLite store alongside the history file | Context-ranked recall |
| Atuin | SQLite history with fuzzy scoped search, many importers, optional encrypted sync, agent hooks, and MCP | The strongest all-round shell-history replacement, Fish or multi-machine users |
| Suvadu | Local SQLite history for Bash and Zsh plus AI-agent session capture | Reviewing and continuing coding-agent work alongside your own history |
fzf and McFly still read or keep your normal history file, so fix recording first. Atuin and Suvadu record new commands through their own shell hooks. For a fuller comparison, see Atuin, fzf, McFly, hstr, and Suvadu compared.
Switching to Suvadu: install, import, search, and roll back
Suvadu records each new command in a local SQLite database with its directory, exit code, duration, session, whether a person or an agent ran it, and inside a git repository the branch it started on. It does not change HISTFILE or your history options: Zsh keeps writing ~/.zsh_history as before. Like HIST_IGNORE_SPACE, it skips commands typed with a leading space. It requires Zsh 5.1+ on macOS or Linux.
Know two 0.5.1 limits before you start: there is no cross-machine sync, so you move history with an explicit export and import, and typed Ctrl+R search ranks only the newest 5,000 matches by relevance (it searches your whole history, and matches beyond that window are paged newest-first).
1. Install
brew tap AppachiTech/suvadu && brew install suvadu Alternatives: curl -fsSL https://downloads.appachi.tech/suvadu/install.sh | bash (verifies a checksum and installs to /usr/local/bin, using sudo only if that directory is not writable; bash -s -- --user installs into ~/.local/bin instead) or cargo install suvadu. Homebrew and Cargo do not edit your shell configuration. Run at a terminal, the install script offers to add the hook line from step 2 to ~/.zshrc, shows it first, and writes it only if you type y, keeping the old file as ~/.zshrc.suvadu-backup.
2. Enable the Zsh hook
Add this line once to ~/.zshrc, then reload; if you let the install script add it, skip the echo line. If another tool such as Atuin, McFly, or fzf binds Ctrl+R or the arrow keys, disable its binding first; the one loaded last wins.
echo 'eval "$(suv init zsh)"' >> ~/.zshrc
source ~/.zshrc
suv doctor The hook binds Ctrl+R to Suvadu search and Up/Down to Suvadu's recency-first recall. To keep native (or zsh-history-substring-search) arrow behavior, set enable_arrow_navigation = false under [shell] in Suvadu's config or in suv settings, then open a new shell. If you use zsh-autosuggestions, Suvadu 0.5.1 can feed it the same recall: add ZSH_AUTOSUGGEST_STRATEGY=(suvadu history) to ~/.zshrc, and it falls back to Zsh's own history when Suvadu has nothing.
3. Import your existing Zsh history
Preview first, then import. The import reads the file and never modifies it, and reports how many commands it imported and skipped.
suv import --from zsh-history ~/.zsh_history --dry-run
suv import --from zsh-history ~/.zsh_history If the file used EXTENDED_HISTORY, imported commands keep their timestamps and durations, and importing the same file again skips entries already present. Plain-format lines have no timestamps; since 0.5.1 they get an explicit 1970-01-01 placeholder time rather than an invented one, and importing the same file again adds nothing. Since 0.5.1 the importer also decodes non-ASCII text correctly, keeps both runs of a command run twice in one second, and skips commands the hook already recorded live. The file never held directories or exit codes, so imported entries do not have them. Multi-line commands are kept together. If the file contains invalid UTF-8 bytes, Suvadu warns and replaces them.
4. Run a first search
echo hello Press Ctrl+R and type hello. The detail panel shows the directory, exit code, and time of the command you just ran. Select it to put it on your prompt; press Enter to run it. Search is literal by default: every typed word must appear in the command, and since 0.5.1 a query that matches nothing is retried ignoring separators, so hiveagent finds hive-agent. Ranking learns how often and how recently you run each command and what you usually run next. Since 0.4.2, Ctrl+X switches to literal, prefix, or fuzzy matching, and Ctrl+P narrows the scope to this directory, this Git workspace, or this session.
Next step: if nothing was recorded or Ctrl+R still opens another tool, work through the troubleshooting guide. The installation guide and shell integration reference cover Zsh setup in more detail.
5. Roll back
- Stop recording without removing anything:
suv pausepauses the current shell;suv disablestops recording everywhere untilsuv enable. The key bindings stay active. - Return to native Zsh history: delete the
eval "$(suv init zsh)"line from~/.zshrcand open a new terminal (or runexec zsh). Re-sourcing the file in the same shell does not remove bindings that are already loaded. - Uninstall: for Homebrew, Cargo and (since 0.5.0) install-script installs,
suv uninstallasks for confirmation, removes the binary, removes that exactevalline from~/.zshrcand~/.bashrc, and removes Suvadu's Claude Code and Codex hooks. For a manual install it only prints the binary's path for you to delete, so remove the~/.zshrcline yourself. See update and uninstall.
Uninstalling keeps Suvadu's database and config so you can change your mind; delete them separately if you want them gone. Your ~/.zsh_history is untouched throughout, and because Zsh kept writing it while Suvadu was active, it still contains the commands from that period, as far as your Zsh settings saved them.
Zsh history: quick answers
Where does Zsh store its history?
In the file named by $HISTFILE, usually ~/.zsh_history. Run echo $HISTFILE to see yours. If HISTFILE is unset, history is kept only in memory and is lost when the shell exits. macOS sets it to ${ZDOTDIR:-$HOME}/.zsh_history in /etc/zshrc, and Oh My Zsh sets ~/.zsh_history if nothing else did.
What is the difference between HISTSIZE and SAVEHIST?
HISTSIZE is how many commands each shell keeps in memory; SAVEHIST is how many are written to the history file. Keep HISTSIZE at least as large as SAVEHIST. The macOS defaults are 2,000 and 1,000; Oh My Zsh raises them to at least 50,000 and 10,000.
Why is my Zsh history not being saved?
Usually HISTFILE is unset, SAVEHIST is 0 or very small, a framework set its own values after yours, or the terminal was killed before the shell could write its history. Check with echo $HISTFILE $HISTSIZE $SAVEHIST and setopt | grep -i hist, and set INC_APPEND_HISTORY or SHARE_HISTORY so each command is written as it is entered.
How do I search Zsh history?
Press Ctrl+R and type part of the command; press it again for older matches. To see every match at once, run fc -l 1 | grep docker. history -i adds the date and time each command ran, if EXTENDED_HISTORY was on when it was saved.
How do I share Zsh history between terminals?
Turn on setopt SHARE_HISTORY: each command is written as it is entered and other open shells read it in. Without it, each terminal loads the file only when it starts; fc -RI reads new entries on demand.
How do I delete a command from Zsh history?
Close your other terminals, run fc -W, remove the line from $HISTFILE, then run unset HISTFILE; exit (with a leading space) so this shell does not write its in-memory copy back. The steps are explained below.
The bottom line
Start with native Zsh: set HISTFILE, generous HISTSIZE/SAVEHIST, EXTENDED_HISTORY, and one of SHARE_HISTORY or an incremental-append option. That fixes most missing history. If you then want richer recall and context, fzf and McFly are small steps and Atuin is the most complete shell-history replacement. Suvadu is worth trying when you also want to see and continue what your coding agents did.
Builder of Suvadu. Writes Rust, thinks about shell history more than most people, and believes developer tools should be local-first.