Why I just deleted 200 lines of shell config
5 mins read

Why I just deleted 200 lines of shell config

Well, that’s not entirely accurate—I was actually staring at my terminal at 11:30 PM last Thursday, trying to figure out why my shell prompt looked like garbage on my staging server but rendered perfectly on my local machine. Both environments pull from the exact same dotfiles repository. Both run Ubuntu 24.04.

It made zero sense. I checked the locale settings. I checked the font rendering. I even checked if my SSH client was stripping out specific unicode characters.

The culprit was a lingering, invisible universal variable I set three years ago that was overriding my prompt configuration. Shell configuration management is usually an afterthought for most of us. We automate our cloud infrastructure with strict declarative code, but our local terminal environments are held together by hope, symlinks, and bash scripts we copied from Stack Overflow a decade ago.

I’ve been running fish as my daily driver for a while now. And I updated my M3 MacBook and my t4g.small EC2 instances to the new 4.3 release this week, mostly because I wanted to see if the promised completion accuracy improvements were real. They are, but the actual reason I care about this update is that it finally fixed how environment variables behave across multiple machines.

The global defaults trap

If you manage your dotfiles in a git repo, you know the pain of syncing shell state. Previously, if you wanted a variable to persist, you used a universal variable (set -U). This writes the value to a physical file (~/.config/fish/fish_variables).

command line interface - Interactive command line interface | Mocks Server
command line interface – Interactive command line interface | Mocks Server

This is a disaster for version control. You end up with machine-specific state permanently saved in a file that you are actively trying to sync across different computers.

The new update introduces proper global variable defaults. You can define a baseline in your config file, and it applies everywhere cleanly without writing to that local state file. But I — well, let me back up—I ran into a massive wall trying to implement this.

I set my EDITOR to nvim using the new global default syntax. I sourced the config. I typed git commit. Nano opened.

I spent an hour digging through GitHub issues before I realized the problem. If you have legacy universal variables saved in your state file, they silently take precedence over the new global defaults. There is no warning. The shell just assumes the local state file is the absolute truth.

You have to nuke your old universal variables manually before the new system works. Here is what I ended up adding to a one-time cleanup script:

# Wipe the legacy universal variables blocking the new defaults
set -e -U EDITOR
set -e -U KUBECONFIG
set -e -U AWS_PROFILE

# Now you can safely use the new global defaults in config.fish
set -g --default EDITOR nvim
set -g --default KUBECONFIG ~/.kube/config_main

Rethinking the prompt

Once I got the variables sorted, I started looking at the refined theme management and the new status language commands.

programmer working late - Focused female programmer working late on a project | Premium AI ...
programmer working late – Focused female programmer working late on a project | Premium AI …

I used to rely on third-party plugin managers to handle my terminal themes. You’d pull down a package, realize it broke half your custom aliases, and spend a weekend trying to patch it. My config.fish was bloated with about 200 lines of custom color-parsing logic just to make my git branch status look decent when I SSH into my servers.

I deleted all of it yesterday.

The new status command makes checking exit codes and formatting them so much cleaner. You don’t need external plugins to build a responsive prompt anymore. I rewrote my entire prompt function to rely solely on the built-in language features.

function fish_prompt
    # Capture the exit status of the last command immediately
    set -l last_status $status
    
    # The new status evaluation is much cleaner to read
    if not status is-interactive
        return
    end

    set_color normal
    echo -n "["(date "+%H:%M:%S")"] "

    if test $last_status -ne 0
        set_color red
        echo -n "x "
    else
        set_color green
        echo -n "✓ "
    end
    
    set_color blue
    echo -n (prompt_pwd)" > "
    set_color normal
end

The performance metric that actually matters

programmer working late - Professional programmer working late in the dark office | Free Photo
programmer working late – Professional programmer working late in the dark office | Free Photo

I work with massive Kubernetes clusters. My kubeconfig file is currently sitting at around 3,400 lines because I manage access to dozens of different client environments.

Hitting tab after typing kubectl get pods -n used to lock up my terminal. The completion engine would hang while it parsed the namespaces. I timed it a few weeks ago out of sheer frustration—it was taking about 1.2 seconds per tab press.

When you run fifty kubectl commands a day, that latency drives you insane.

I benchmarked the exact same operation after upgrading the shell and letting the new completion accuracy engine do its thing. The parse time dropped to 45ms. It is practically instantaneous now. I don’t know exactly what they changed in the parsing logic under the hood, but it completely removed the bottleneck in my workflow.

I’m pushing these config changes to the rest of my server fleet this afternoon. Probably should have done this months ago.

Leave a Reply

Your email address will not be published. Required fields are marked *