Skip to main content

Shell

Shell prompts run on every keypress that produces a new prompt line. If your prompt calls git status, kubectl config current-context, or similar tools, you are forking a process — often several — dozens of times per minute. On a busy workstation with many terminal windows open, this adds up quickly and causes visible prompt lag.

Beachcomber solves this by keeping those values in a shared cache updated by a background daemon. Your prompt calls comb g, which reads from that cache over a Unix socket in roughly 34 microseconds. The underlying tool (git, kubectl, etc.) runs on a timer in the background, not on your prompt's critical path.

Prerequisites

Before adding shell integration, make sure comb is installed and on your PATH. Run comb --version to confirm.

The daemon is socket-activated — it starts automatically the first time your prompt runs comb g, and shuts down after an idle period. You do not need to launch it manually. At any point, run comb s to confirm the daemon is reachable and see cache statistics.

zsh

Where to put it

Add the precmd function to your ~/.zshrc. The precmd hook runs after each command completes, just before zsh draws the next prompt — making it the right place to refresh prompt variables.

Setup

# ~/.zshrc
# comb g returns plain text by default. g = get, no suffix needed.
# See /docs/reference/cli-commands for all shorthands.
precmd() {
local branch dirty untracked
branch=$(comb g git.branch . 2>/dev/null)
dirty=$(comb g git.dirty . 2>/dev/null)
untracked=$(comb g git.untracked . 2>/dev/null)

local git_part=""
if [[ -n "$branch" ]]; then
git_part="%F{blue}${branch}%f"
[[ "$dirty" == "true" ]] && git_part+="*"
[[ "$untracked" -gt 0 ]] && git_part+="?"
git_part+=" "
fi

PS1="${git_part}%F{green}%~%f %# "
}

The . argument passed to comb g git.branch . tells the daemon to scope the lookup to the current directory. Path-scoped providers like git return data relative to the nearest repository root from that path. Always pass . (or an explicit path) when querying providers that are directory-aware.

How to reload

source ~/.zshrc

Expected result

Open a new terminal in a git repository. Your prompt should show the current branch name. Move into a directory with uncommitted changes and the * indicator should appear. In a directory outside any repository the git portion of the prompt will be absent.

bash

Where to put it

Add the function and PROMPT_COMMAND assignment to your ~/.bashrc. bash evaluates PROMPT_COMMAND before drawing each prompt, equivalent to zsh's precmd.

Setup

# ~/.bashrc
# comb g.s is shorthand for `comb get -f shell`. g = get, .s = shell format (key=value output).
__beachcomber_prompt() {
# Fetch entire git state in one query, parse key=value output
local git_state
git_state=$(comb g.s git . 2>/dev/null)

local branch dirty
while IFS='=' read -r key value; do
case "$key" in
branch) branch="$value" ;;
dirty) dirty="$value" ;;
esac
done <<< "$git_state"

local git_part=""
[[ -n "$branch" ]] && git_part="(${branch}${dirty:+*}) "

local kube
kube=$(comb g kubecontext.context 2>/dev/null)
local kube_part=""
[[ -n "$kube" ]] && kube_part="[${kube}] "

PS1="${kube_part}${git_part}\w \$ "
}

PROMPT_COMMAND=__beachcomber_prompt

The bash example queries the entire git namespace at once with comb g.s git ., which returns all fields as key=value lines. Parsing that single response is more efficient than making one comb g call per field. The kubecontext.context query has no path argument because the current Kubernetes context is global, not directory-scoped.

How to reload

source ~/.bashrc

Expected result

Open a new terminal. In a git repository the prompt shows (branch) ~/path/to/repo $. If your kubeconfig has a current context set, it appears as [context-name] before the git section. Outside a repository the git section is omitted entirely.

fish

Where to put it

Create or replace ~/.config/fish/functions/fish_prompt.fish. fish auto-loads this file and calls fish_prompt before each prompt. If you already have a custom prompt, merge the beachcomber calls into your existing function.

Setup

# ~/.config/fish/functions/fish_prompt.fish
function fish_prompt
set -l branch (comb g git.branch . 2>/dev/null)
set -l dirty (comb g git.dirty . 2>/dev/null)
set -l battery (comb g battery.percent 2>/dev/null)

set -l git_info ""
if test -n "$branch"
set git_info " $branch"
test "$dirty" = "true"; and set git_info "$git_info*"
end

set -l bat_info ""
if test -n "$battery"
set bat_info " $battery%%"
end

echo -n (set_color blue)(prompt_pwd)(set_color normal)$git_info$bat_info" > "
end

fish has no subshell penalty for command substitutions — each (comb g ...) call is still a socket round trip, but there is no fork overhead. The battery.percent field has no path argument because battery state is a machine-level value.

How to reload

source ~/.config/fish/functions/fish_prompt.fish

Or open a new terminal. fish reloads function files automatically at startup.

Expected result

Your prompt shows the current directory in blue, the git branch if present, a * if the working tree is dirty, and the battery percentage if the battery provider is enabled in your config.

Testing your integration

Before restarting your shell, verify that the daemon is returning data:

# Confirm the daemon is reachable
comb s

# Query a path-scoped provider from within a git repo
comb g git.branch .

# Query a global provider
comb g kubecontext.context

If both commands return values, reload your shell config or open a new terminal. Your prompt should immediately reflect live data without any noticeable delay.

To confirm data freshness, make a change in a repository (stage a file) and press Enter at the prompt. The dirty indicator should appear within one polling cycle (default: a few seconds).

Performance

Each comb g call returns a cached value over a Unix socket. Typical round-trip time is around 34 microseconds. Compare that to the tools beachcomber replaces:

SourceTypical cost
comb g (cached)~34 µs
git rev-parse --abbrev-ref HEAD5–15 ms
kubectl config current-context50–150 ms

A prompt that used to call git and kubectl directly could take 60–170 ms to draw. With beachcomber it takes under 1 ms. The difference is most noticeable when switching between many terminal tabs quickly or working over a slow filesystem.

The daemon handles the expensive work on its own schedule, independent of your prompt.

Provided Scripts

The repository ships two curl-able integration scripts in scripts/:

  • scripts/polyfill.sh — Defines a comb() shell function that stands in for the real binary. If comb is installed, the script does nothing. If comb is not installed, it handles comb g <key> calls by falling back to native tools (git, hostname, uptime, etc.) for known keys. This lets integrations write comb g git.branch . and have it work everywhere — with or without beachcomber. See the Polyfill guide for the full key list.

  • scripts/chpwd.sh — A directory-change hook that pokes path-scoped providers (git, mise, terraform, python, direnv, asdf) in the background whenever the working directory changes. Warms the cache before your first prompt renders in a new directory. Supports zsh (chpwd), bash (PROMPT_COMMAND), and fish (separate config file). No-op if comb is not installed.

# Install via curl (bash/zsh)
source <(curl -fsSL https://beachcomber.sh/scripts/polyfill.sh)
source <(curl -fsSL https://beachcomber.sh/scripts/chpwd.sh)

# Or download and source from a local path
curl -fsSL https://beachcomber.sh/scripts/polyfill.sh -o ~/.local/share/beachcomber/polyfill.sh
source ~/.local/share/beachcomber/polyfill.sh

Troubleshooting

  • Prompt shows no dynamic data: the 2>/dev/null in the examples silences errors, so if the daemon is not running you get an empty prompt with no error message. Remove 2>/dev/null temporarily to see what comb g reports.
  • Wrong branch after switching: the cache updates on its next poll cycle. Run comb r git . to force an immediate refresh.

See the Troubleshooting guide for general diagnostics.