May 5, 2026·4 min read·engineeringfishhistorypersistence

fish 4.7 fixes history corruption during termination

fish 4.7 prevents NUL-byte history corruption on SIGTERM and SIGHUP. Its history implementation shows why saving shell history involves coordination between live sessions.

Fish 4.7.0, released on May 5, fixes history corruption with NUL bytes when the shell receives SIGTERM or SIGHUP. The release notes link the repair to a long-running history report. The immediate change concerns persisted history during termination. It should not be expanded into a claim that every possible history-search problem has the same cause.

The distinction is useful because shell history has several consumers. It supplies commands for explicit searches and for suggestions while the user types. A damaged history file can therefore appear to be an editing problem even when the underlying failure concerns persistence. Fish's interactive interface makes retained commands useful often enough that saving them correctly becomes part of everyday shell behavior.

History-based suggestions helped make fish compelling to me after Bash and Zsh. Omer introduced me to it, and the amount of useful interaction available without extensive configuration was the attraction. That preference makes a history persistence fix more relevant than its short release-note entry might suggest. The suggestions depend on information that must remain usable across sessions.

History is shared between processes

The history implementation at the 4.7.0 tag begins by describing support for multiple shells writing history concurrently. Ordinary writes append records. Maintenance can instead rewrite the history, discard older entries, remove duplicates, and replace the file. These are different operations with different effects on other processes that already have the file open.

The implementation maps history files into memory and retains offsets for lazily loading entries. That reduces the amount of per-entry information the process must keep in memory. It also means that a session's view is related to a particular file and its recorded structure. A rewrite is not simply another append to the same sequence of bytes.

The code's rewrite path constructs a temporary file, flushes buffered output, and uses the filesystem helper to replace the destination. After a successful rewrite, the history object clears its cached file state so it can refer to the file again. Its append path also checks whether the file identity has changed and clears the cached state when another replacement is detected.

These details explain the coordination problem without attributing an unverified mechanism to the termination bug. Multiple shells can hold state about one logical history while the file representing it changes. Correctness requires handling both concurrent writers and readers whose current view refers to an earlier file.

The lock must survive replacement of the data file

The filesystem helper documents why it locks a parent directory instead of placing the lock directly on the data file. If a process opens the file and another process replaces it before locking, the two processes can hold different files. Locks on those different files do not provide the intended exclusion.

Locking a path that does not change with the data-file replacement addresses that particular race. The helper distinguishes shared access from exclusive access, and distinguishes appending from renaming a replacement into place. Those modes let callers request coordination appropriate to the operation they are performing.

The history code uses the append mode when adding records. Rewriting uses a helper designed for a temporary-file replacement. The implementation therefore treats file replacement as part of the persistence protocol, with corresponding cache and locking behavior. Describing history as a text file alone leaves out those process-level relationships.

The release's stated guarantee remains specific to the repaired corruption on the named signals. It does not promise recovery of an already damaged history file, and it does not claim that any filesystem or termination condition can preserve every pending entry. The relevant improvement is that these termination paths no longer introduce the documented NUL-byte corruption.

Prompt text also needs a boundary around external data

Fish 4.7 changes prompt_pwd to strip control characters. Its implementation states the reason, avoiding terminal escape-sequence injection through a path included in the prompt. The function removes control characters before shortening directory components for display.

That ordering matters. Directory names are data received from the filesystem, while the prompt becomes output interpreted by a terminal. Shortening a path is a presentation operation. Removing control characters prevents part of the path from being treated as terminal instructions during that presentation.

This is another aspect of the shell's role between stored information and interactive display. History turns saved commands into suggestions. A prompt turns a current directory into visible context. Both require more care than copying bytes to the screen, although the persistence fix and the control-character filtering solve separate problems.

I like fish's decision to provide these interactive helpers as maintained defaults. A custom prompt should not need to rediscover every issue involved in presenting a path. The supplied helper can handle its display policy centrally, while prompt authors decide where the directory belongs in their layout.

Repainting preserves the active interaction

The same release stops repaint events from resetting the completion pager and other transient interface state. Changing a color variable or requesting a repaint can redraw the screen without discarding the operation the user has underway. That separates refreshing presentation from restarting interaction. The distinction is visible to a user choosing among completions, because a visual update no longer requires reconstructing that selection.

Generated completions also gain support for groff device-control escapes used in newer generated manual pages. The release notes identify help2man output as the affected input. Completion generation depends on interpreting documentation produced by another tool, so a formatting change there can affect what fish offers at the prompt.

These changes affect different stages of command entry. The history repair preserves stored input through termination, prompt filtering constrains how path text reaches the terminal, and repaint handling retains the active completion selection. Each repairs a concrete dependency of the interactive behavior that makes fish useful as a personal shell.