I spend most of my working day looking at a shell prompt, and for years mine told me almost nothing. A username, a hostname, the current directory. None of which I needed, because I knew who I was, where I was logged in, and roughly where I'd cd'd to. The prompt was decoration. Worse, it was decoration in the one spot on screen I look at most.
So I rebuilt it around a simple question: what do I actually need to know at the exact moment I'm about to type a command? The answer turned out to be small, but every item earns its place.
what's on it
Four things, in the end.
The git branch and working-tree state, because most of my commands happen inside a repository and "which branch, and is it dirty" is the thing I check most often. A clean tree shows the branch in one colour, a dirty tree in another, with a marker if there's anything staged or unstaged. That single glance has saved me from committing to the wrong branch more times than I'd like to admit.
The exit status of the last command, but only when it's non-zero. A green prompt that turns red the instant something fails is worth a hundred echo $? calls. When everything's fine the prompt stays quiet. When something broke, it shouts, and it tells me the code.
A marker when I'm in a context that isn't my normal one: an SSH session, or a different Kubernetes context, or a Python virtualenv. The point is to make "you are not where you think you are" impossible to miss, because that's the state where mistakes get expensive.
And the time the prompt was printed. Trivial, but when I scroll back up a long session it's a free timeline of when each command ran.
the problem with git in a prompt
The obvious way to show git state is to shell out to git every time the prompt renders. That works fine until you cd into a large repository, at which point every single prompt redraw runs git status, and on a big tree that's not free. You hit return on an empty line and there's a perceptible pause before the prompt comes back. Do that a few hundred times a day and it grates.
The first mitigation is to ask git the cheapest possible questions. You don't need the full git status output to render a prompt. You need the branch name, which is a one-line read, and a yes/no on whether the tree is dirty:
branch=$(git symbolic-ref --short HEAD 2>/dev/null)
if [ -n "$branch" ]; then
if ! git diff --quiet --ignore-submodules HEAD 2>/dev/null; then
dirty="*"
fi
fi
git diff --quiet exits non-zero if there are changes and prints nothing, which is exactly what you want: it's a question, not a report, and it's far cheaper than parsing porcelain output.
That got it most of the way, but on the very largest repositories it was still noticeable. So the second step was to stop doing the work on the critical path at all.
going async
The idea is simple: render the prompt immediately with whatever git state you computed last time, kick off the real git check in the background, and when it finishes, redraw the prompt with the fresh answer. The prompt never blocks. In the rare case where the displayed state is a few hundred milliseconds stale, who cares, it catches up before you've finished reading it.
In zsh this leans on the zsh/async machinery, or you can hand-roll it with a background job that writes its result to a temp file and signals the shell to re-render. The shape is:
precmd:
show prompt using cached git info
fire off background git check
background job completes:
write result to cache
trigger a redraw
The effect is that the prompt is instant everywhere, regardless of repository size, and the git information is correct within a heartbeat. That's the right trade. I'd rather have an always-fast prompt that's occasionally a moment behind than a perfectly current one that stutters.
resisting the urge to add more
The hard part of a prompt isn't building it, it's stopping. There's a whole ecosystem of frameworks now that will put a dozen segments on your prompt: battery, weather, the phase of the moon. I tried a few. They look impressive in a screenshot and they're noise in practice, because a prompt that shows everything shows nothing. Your eye has nowhere to land.
So I held the line at four things, and the rule for adding a fifth is strict: it has to be something I'd otherwise run a command to find out, and I have to want it more often than not. Most candidates fail that test. The directory path I already know. The hostname I set in the terminal title instead, where it belongs.
It took an afternoon and it's one of the better afternoons I've spent on my setup, because unlike most tweaks it pays out every single day. The prompt went from decoration to a small instrument panel: branch, dirty, exit code, context. Quiet when things are fine, loud when they're not. That's the whole brief, and it turns out that's all I needed.