July 20, 2022·4 min read·engineeringfishhistoryprivacy

fish 3.5.1 rejects history merging in private mode

A small history-command guard protects the separation between a private interactive session and ordinary shared history.

Merging history is meaningful when a shell session participates in persistent history. A private session deliberately does not. fish 3.5.1, released on July 20, makes history merge an error in private mode. The versioned changelog identifies history loss as the reason for rejecting an operation whose normal purpose is to incorporate commands from other sessions.

The original issue 9050, opened on July 1, gives a short reproduction on fish 3.5.0. A user starts a private shell, enters commands, observes them in history, and invokes the merge operation. The visible history then becomes blank. The report says the behavior occurs without third-party customizations.

I like history as an interactive aid because it reduces repeated work at the prompt. That makes its lifetime and sharing rules worth defining carefully. Private mode should support the current interaction without accidentally invoking the persistence behavior that an ordinary session expects.

Private history still has a current session

The fish invocation reference at 3.5.1 documents --private as disabling access to old history and storage of new history. It does not describe the mode as preventing every in-memory recall operation. The issue's reproduction depends on that distinction because commands entered in the private session are initially visible there.

fish --private

That command starts a shell with the private-mode history policy. Commands executed within it can still be useful during the ongoing session, while the documented promise concerns loading existing history and storing newly entered commands. The two lifetimes should not be confused.

The versioned history-command reference also describes history names. Ordinary interactive history uses a session name that determines the persistent history file. Setting an empty history name disables storage, and a different nonempty name selects a different history collection.

These controls make history more specific than a single global list of all command lines. The selected collection and whether it persists are part of the session's behavior. An operation that reaches outside the current in-memory commands needs to respect those choices.

Merge crosses a session boundary

For an ordinary session, the documented purpose of history merge is to incorporate changes from other sessions immediately. fish ordinarily ignores changes from sessions started after the current one. Explicit merging lets the user bring those commands into the current view without waiting for a new shell.

That is useful for simultaneous interactive work. One terminal can acquire commands entered in another. It is also precisely the kind of operation that does not fit the private-mode contract, which excludes accessing old history.

The 3.5.1 implementation checks private mode before calling the function that incorporates external changes. If private mode is active, it writes an error and returns an invalid-arguments status. The merge operation does not proceed.

This is a targeted guard at the command boundary. It does not attempt to give external merging a new interpretation inside a private session. Rejecting the operation preserves the distinction between a temporary history view and the persistent collection used by ordinary shells.

The changelog's wording describes protection against wiping other sessions' history, while the initial issue directly demonstrates loss of the private session's visible history. Those observations should not be expanded into a claim that private commands were being disclosed. The documented correction is the rejection of merging, and the report establishes the visible-history failure that motivated investigation.

History operations have different scopes

The history reference separates search, deletion, clearing, and persistence operations. Search returns matching entries and normally orders them newest first. Saving writes outstanding changes immediately, although fish normally saves automatically. Clearing removes the history file's contents, which is substantially different from incorporating another session's additions.

There is also a clear-session operation. Its documented scope is the current session's activity, but the reference warns that merging changes the relevant boundary. After a merge, clear-session erases only the history after that merge. This is another example of why merging is not just a display refresh.

I prefer an explicit error for an incompatible history operation to behavior that silently changes the session's recall set. A shell can be convenient while still refusing a command whose semantics conflict with the selected mode. The error also gives a function or binding invoking the command a failure result it can handle.

The same patch release enables Vi cursor shaping by default inside tmux when the outer terminal supports it. That change concerns feedback about editing mode, while the history fix concerns persistence boundaries. Both affect interactive behavior without changing the syntax of ordinary commands. For private history specifically, fish 3.5.1 now checks the session policy before it attempts to merge external changes.