Notes on how I have fish shell set up on macOS. Fish is the shell I land in every time I open Alacritty, so I spend a lot of time here.

Why Fish

Bash and zsh are fine, but fish gives me good defaults out of the box. Syntax highlighting as I type, autosuggestions from history, and a sane scripting language. It is not POSIX, which means I cannot paste random shell snippets and expect them to work, but for daily interactive use I prefer it.

Fish lives at /opt/homebrew/bin/fish (installed via Homebrew) and is set as the shell in my Alacritty config.

Auto attach to tmux

The first thing my config does after loading Homebrew is jump straight into tmux:

if status is-interactive
and not set -q TMUX
    exec tmux new -As0
end

tmux new -As0 either attaches to session 0 if it exists, or creates it. Combined with exec, the fish process is replaced by tmux, so I do not have a stray shell hanging around outside the session.

The check on TMUX is important. Without it, every pane fish opens inside tmux would try to start tmux again.

Greeting and basics

set fish_greeting

Empty greeting. I do not need fish to tell me hello every time.

set -gx GPG_TTY (tty)
set -U EDITOR nvim

GPG_TTY is required for GPG signing to prompt for the passphrase. I sign commits, so this matters. EDITOR is universal so git, fzf and anything else that calls into $EDITOR picks neovim.

Version managers

I have a few language version managers installed. They are gated on status --is-interactive so they do not slow down non interactive sessions:

# rbenv
if status --is-interactive
  set -x PATH $PATH "$(brew --prefix)/shared/rbenv"
  rbenv init - | source
end

# pyenv
if status --is-interactive
  set -Ux PYENV_ROOT $HOME/.pyenv
  set -U fish_user_paths $PYENV_ROOT/bin $fish_user_paths
  pyenv init - fish | source
  pyenv virtualenv-init - | source
end

I also have tfenv on PATH (for Terraform) and sdkman for the JVM languages, set up through a conf.d snippet that ships with the sdkman fish plugin.

Locale

I set every LC_* variable to en_US.UTF-8. Worth being explicit because some tools (ruby, postgres, certain ssh setups) will silently fall back to C locale otherwise, which breaks UTF-8 handling:

set -gx LANG "en_US.UTF-8"
set -gx LC_ALL "en_US.UTF-8"
# ...and the rest

PATH and SDK paths

Android SDK, JDK 17 include path for native builds, libomp for compiling some Python wheels, and a few extras:

set -gx ANDROID_HOME /Users/ferbass/Library/Android/sdk
set -gx PATH $ANDROID_HOME/tools $ANDROID_HOME/platform-tools $PATH
set -gx CPPFLAGS "-I/opt/homebrew/opt/openjdk@17/include"
set -gx LDFLAGS "-L/opt/homebrew/opt/libomp/lib"

Plus ~/.local/bin and ~/.tfenv/bin near the front of PATH.

Disable Homebrew analytics

set -gx HOMEBREW_NO_ANALYTICS 1

Small thing, but I do not need Homebrew sending usage data.

Aliases

I keep aliases short and few:

alias assume="set -x GRANTE_SAML true; source /opt/homebrew/bin/assume.fish"
alias tns="tmux new -d -s"
  • assume is for the granted AWS credential tool. The env var puts it in SAML mode.
  • tns foo creates a detached tmux session named foo. Handy when I want to spin up a background workspace without leaving the current one.

Functions

These live in fish/functions/. Each function gets its own file, which fish auto loads on first call.

function cat
    bat $argv
end

cat is shadowed by bat for the syntax highlighting and line numbers.

function ls --wraps='eza --icons --group-directories-first'
    eza $argv --icons --group-directories-first
end

ls is shadowed by eza for icons and directories first.

function vim
    if type -q nvim
        command nvim $argv
    else
        echo "nvim is not installed"
    end
end

vim runs nvim if it is installed. Keeps muscle memory working.

A couple of small utilities:

function myip
    curl ifconfig.me
end

function lockscreen --description '[macOS] Lock your screen without Log out the user'
    pmset displaysleepnow
end

I also have a few git helpers like default_branch <url> (uses ls-remote --symref to pull the default branch name) and ensure_remote <name> <url> (adds or updates a remote).

Tmux + AI status icon

When I run claude or gemini inside tmux, I want a robot icon to appear in the tmux window name so I can see at a glance which window has an AI session running. Fish has fish_preexec and fish_postexec events that fire before and after every command:

function __tmux_ai_preexec --on-event fish_preexec
    if set -q TMUX
        set cmd (string split ' ' $argv[1])[1]
        switch $cmd
            case claude claude-personal
                tmux set-option -p @ai_running "🤖"
            case gemini
                tmux set-option -p @ai_running "🤖"
        end
    end
end

function __tmux_ai_postexec --on-event fish_postexec
    if set -q TMUX
        tmux set-option -pu @ai_running
    end
end

The tmux side picks this up through a window name format that reads @ai_running. The result is a small robot icon next to the window title while the command is running.

Keybindings

In conf.d/keybindings.fish:

bind \e. history-token-search-backward

This gives me Alt+. to paste the last token from the previous command, which is the readline behaviour I am used to from bash and zsh. Fish does not ship it by default, so I add it back.

Prompt

I use a small custom prompt in functions/fish_prompt.fish. It shows the current directory (shortened), the git branch, and the exit code in red if the last command failed. Two lines, so the command always starts at column 1.

Local secrets

Last line of the config sources a gitignored file for anything machine specific:

if test -f ~/.config/fish/config.local.fish
    source ~/.config/fish/config.local.fish
end

That is where any API tokens or credentials live, so they never end up in the dotfiles repo.

Plugins

Managed by fisher. The fish_plugins file lists what is installed:

jorgebucaran/fisher
reitzig/sdkman-for-fish@v2.0.0

I keep the plugin list short on purpose. Most of what other people use plugins for, fish already does natively or I have a small function for.

That is it

If you want to copy any of this, it all lives in my dotfiles repo under fish/.

This post was put together with help from an AI assistant.