Back to posts

What's new in fish 4.0

Feb 27, 2025

We’re proud to announce the release of fish 4.0.

This release represents 2731 commits by over 200 people. It has 1185 files changed, 111221 insertions(+), 89168 deletions(-)1.

As you might know, fish was entirely rewritten in rust. But, while that’s a lot of work, it should also be invisible to you, the user - if we’ve done our job right.

Thankfully, this release is also full of cool new features and other improvements.

We’ve written about the port before, the rest of this post will tell you what awaits you if you’re interested in using fish 4.

For the full list of changes you can see the Release Notes, this is the highlights, curated and explained.

So, what was changed for me, then?

Bind Notation and Improved Key Chord Support

Fish’s bind now has a key notation. That means you no longer have to write bind \e\[1\;5C or bind \e\x7F, which directly reflects the escape sequences the terminal sends, you can just write bind ctrl-right or bind alt-backspace. This requires fish to understand what \e\[1\;5C is and to map it to an understanding of keys.

And this understanding of keys now goes deeper: Fish tries to get the terminal to encode keys better so that it can differentiate them better.

To explain, the usual protocol terminals use to send keys to applications have a lot of limitations: control+I (or control+shift+i) sends the exact same sequence as control+i, which sends the exact same sequence as the Tab key. A lot of keys cannot be disambiguated this way!

So fish now enables a variety of terminal features, including xterm’s “modifyOtherKey” and the kitty keyboard protocol2, that tell the terminal that fish understands when it sends better sequences. If this works, you can bind ctrl-i, bind ctrl-I and bind tab separately! If it doesn’t work, you can still use bind ctrl-i, but it will also be triggered when you press tab.

It does this automatically and without requiring configuration. We have tested a lot of terminals and found that almost all terminals we could get our hands on either understood these sequences and acted on them or harmlessly ignored them.

In some rare cases we encountered terminal bugs that we worked around by disabling these protocols. For instance we found issues in the integrated terminal in JetBrains IDEs or Wezterm3 and iTerm that we hope will be fixed soon. In other cases we have sent patches, for example we improved Zellij’s support for the kitty keyboard protocol.

If you do still see issues, like a mysterious “5u” or “=0” appearing in your commandline, you can disable these protocols for the time being with:

set -Ua fish_features no-keyboard-protocols

Note that this is very likely a terminal bug, so it should be reported there.

The key notation is used by default, but we have added some exceptions so your existing bindings keep working. For example if a binding has a control character (like \e) inside, it will still be interpreted the old way. Our research tells us this should cover just about all bindings in the wild.

Commandline

Continuing on the theme of using terminal features if they are available, fish now also marks the prompt and command output with the OSC 133 sequences. That means it now better integrates with your terminal. In some cases terminals shipped “shell integration” scripts that wrote these in a hacky way, fish now just does it for them.

Similarly, fish now prints cursor change sequences for vi-mode in any terminal. Before, it would try to detect if the terminal was capable of it, by sniffing for environment variables. Unfortunately, terminfo is in practice broken and useless, so this was necessary at the time. But now these sequences are widespread enough that we can just use them and it won’t break terminals.

Fish now also handles commandlines that are longer than the screen correctly. Previously it would get confused and misrender them, now they scroll.

When you have a binding that makes a change with commandline, it now applies immediately. For instance if you do commandline -i foo that inserts “foo” into the commandline right away, where before it would add that change to the input queue. This makes it easier and in some ways possible to script your own bindings.

The ctrl-r history pager (which first shipped in fish 3.6.0) has a few neat improvements - you can now search for substrings with * glob syntax - git*HEAD will search for all history entries containing “git” and “HEAD” in that order.

It also only applies to the current command, so you can build bigger multiline scripts with it, instead of searching for the entire script.

The default theme had a slight adjustment: Instead of rendering commands as “blue”, it now leaves them in the terminal’s “normal” color. For context, the default theme is supposed to be usable on as many systems as possible, which is why it restricts itself to the 16 color palette. But making that readable on as many terminals as possible, no matter their palette, whether light or dark, can be tricky. And we discovered that the default “blue” color on many terminals was hard to read on their default terminal background. We think that’s a bit silly and the terminal’s palette should be improved, but we also need to support existing setups. So we changed it, which also makes the default theme a bit less overloaded with color4.

If you are already using the default theme, it will stay on the old version. To get the new colors, reload the theme via fish_config.

Abbreviations

Abbreviations had one big change: It is now possible to make an abbreviation only valid as an argument to certain commands. For instance, you can use abbreviations with git instead of using git’s own aliases.

In the simple case this would just be abbr --command git co checkout, which will expand co to checkout when used with git, but this will expand any co to checkout. You can add a function to parse git’s arguments in order to see if it’s a subcommand, or you could see if it’s a commit-ish.

Self-installable Builds

There is also a new way to install fish: You can now build it as a “self-installable” binary. That means you can compile a fish binary that includes all the functions, the man pages, the webconfig tool, embedded inside itself. When you start it, it will ask to extract these to your home directory when they are missing or need to be updated.

You can use that to make statically-linked binaries that you can move around. So you can make a fish that works on, for example, any x86_64 linux.

One reason we hear for not using fish is that people ssh to servers where they won’t have it. This change makes it easy to put it there, even if you don’t want to or can’t install a package. No more root permissions, no fiddling with package sources. Just copy a file and run.

And creating a build like that is easy: cargo build will create it by default, so cargo install will install it.

We have also built these for x86_64 (aka “amd64”) and aarch64 (aka “arm64”) linux and uploaded them to the 4.0 github releases page5.

Ignoring Commands In History

An older request was to make it possible to not add certain commands to history.

Other shells feature e.g. the “HISTIGNORE” variable that you can set to an expression for commands that should be ignored, like

HISTIGNORE="ls:[bf]g:exit:pwd:clear:mount:umount:[ \t]*"

Which is a bit un-fishy. So instead of introducing a special expression, we made it a function: fish_should_add_to_history. When you define this, you can choose to remove specific commands from your history. They will still stay as a single, temporary last entry, so you can always rerun the previous command, but once you run something else they’re gone. This is like how fish handles commands prefixed with a space - and if you define your own fish_should_add_to_history that also removes special handling for space-prefixed commands so you can decide to disable that feature.

Scripting Features

Fish’s scripting language also gained a bunch of new features. Most of these are a tad smaller, so let’s go through them:

Overall, we hope you’ll like this new release, which is a big step for fish’s future, but also a good one by itself.

  1. This is discounting translations, since they easily baloon the line count (+420k, -373k) 

  2. The “kitty keyboard protocol” was invented by kitty, but is also supported by a variety of other terminals, including ghostty or alacritty. See kitty’s documentation for details on it. 

  3. Wezterm’s kitty keyboard protocol support is disabled by default, so we did not work around it. 

  4. The term of art here is “angry fruit salad”. It’s easy to make a theme that overuses color, especially if you’re limited to the 16 color palette. 

  5. Other systems are also possible, but we would like to focus on the big ones here because it is easy enough to make these yourself.