- Shell 100%
Bare `disown` after `&` was meant to hide the background self-fetch from bash's job table, but in interactive shells with at least one other job (e.g. a Ctrl-Z'd foreground program) it doesn't actually remove the just-backgrounded job. The fetch then becomes %+, demotes the user's stopped job to %-, and a subsequent bare `fg` targets the fetch instead — typically failing as `fg: current: no such job` once the fetch has completed in the meantime. Pass `$!` explicitly so disown picks the job we just spawned. |
||
|---|---|---|
| .assets | ||
| .claude | ||
| .gitignore | ||
| CLAUDE.md | ||
| gitprompt.bash | ||
| LICENSE | ||
| README.md | ||
gitprompt.bash
A two-line bash prompt with a compact git status segment.
The image above shows GITPROMPT_SHORT=1.
The world does not need another git bash prompt. It already has bash-git-prompt, posh-git-bash, git-radar, gitstatus, starship, oh-my-posh, and a thousand snippets pasted into
~/.bashrcover the last twenty years. So this is just one more — built iteratively with an AI assistant, exactly the way I wanted it. The bar for "yet another prompt" is low when most of the work is conversation.
Layout
<HH:MM> [⌁] <user>@<host>:<cwd> [<env>] ├N <where>[op]<*> <sym> <upstream> <⇣N><⇡N> M<N> ?<N> ⚑<N> ±<N> <duration> (<build>)
$ █
| Segment | Meaning | Color |
|---|---|---|
HH:MM |
current local time (\A); omitted when GITPROMPT_SHORT=1 is set |
dim |
⌁ |
shown when $SSH_CONNECTION or $SSH_TTY is set — you're in a remote shell |
dim red |
user@host |
\u@\h; omitted when GITPROMPT_HIDE_USERHOST=1 is set |
green |
cwd |
\w, last 3 segments; GITPROMPT_SHORT=1 shortens it to ~/foo/…/bar |
blue |
env |
active language env: Python (venv) / (conda) and/or Go ⌬<go.work-dir> |
teal |
├N |
linked worktrees; dim when in the main working tree, teal when inside a linked worktree | dim / teal |
where |
branch (or #tag / @sha when detached); appends a teal ◆ when the checked-out branch is exactly tagged |
green / magenta |
[op] |
operation in progress: rebase, merge, cherry-pick, revert, bisect |
red |
+ |
working tree dirty | yellow |
≡ ← → ≠ ? |
sync vs upstream: equal, behind, ahead, diverged, unknown | green / blue / green / red / dim |
upstream |
upstream branch name (OSC 8 hyperlink to the branch page — path picked from git config gitprompt.forge, see Forge detection); collapsed to <remote>/… when the upstream branch matches the local branch |
red |
⇣N ⇡N |
commits behind / ahead | blue / green |
MN |
tracked files with changes | red |
?N |
untracked files | teal |
⚑N |
stash entries | magenta |
±N |
submodules not in clean state | yellow |
<duration> |
runtime of the previous foreground command, if ≥ GITPROMPT_DURATION_THRESHOLD |
dim |
(<build>) |
how long it took to build the git segment itself (ms / s); shown only when ≥ 100 ms | dim |
$ |
shell prompt character (# for root); colored by exit status of the previous command |
green on success / red on failure |
Install
git clone https://example.invalid/gitprompt.git ~/.gitprompt
echo 'source ~/.gitprompt/gitprompt.bash' >> ~/.bashrc
Source it as the last line of ~/.bashrc so nothing else clobbers
PS1 or PROMPT_COMMAND afterwards.
Outside a git working tree, your existing PS1 is restored unchanged —
gitprompt's two-line variant only appears inside a tree.
As a Claude Code statusline
gitprompt.bash auto-detects how it was invoked: sourced from ~/.bashrc
it installs the interactive prompt; executed directly it reads the
statusline JSON from stdin, runs the same renderer, strips the readline
markers, and prints one assembled line.
The executed-mode line is fitted to $COLUMNS (callers like Claude
Code or pi pass their available budget that way; falls back to stty size </dev/tty, then to no limit). When the line doesn't fit, segments
are progressively dropped: self-update hint → build time → command
duration → wall clock → cwd collapses to ~/foo/…/bar → user@host →
language env. The git segment and the SSH marker are never dropped.
Wire it up in ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash ~/.gitprompt/gitprompt.bash"
}
}
Requires jq.
Claude Code only renders the upstream segment's OSC 8 hyperlink as
clickable when it has detected hyperlink support in your terminal.
Windows Terminal (the common WSL host) isn't in that auto-detect list;
the link text appears but doesn't click. Set FORCE_HYPERLINK=1 in your
shell environment before launching Claude Code to override the
detection — export FORCE_HYPERLINK=1 in .bashrc (before the line
that sources gitprompt.bash) is the simplest fix. gitprompt doctor
reports whether the variable is currently set and prints this hint when
it isn't.
Configuration
| Knob | Default | Effect |
|---|---|---|
GITPROMPT_FAST=1 (env) |
unset | Skip git status derived info (dirty marker, MN, ?N, submodules). Use in huge repos. |
git config gitprompt.fast true (per repo) |
unset | Same as above, scoped to one repo. |
GITPROMPT_TIMEOUT (env) |
0.5s |
Time budget for git status. Repos that exceed it auto-degrade to fast mode for the rest of the session. |
GITPROMPT_DURATION_THRESHOLD (env) |
2 |
Show command duration only if it ran for at least this many seconds. |
GITPROMPT_SHORT=1 (env) |
unset | Hide the wall clock and shorten cwd to ~/foo/…/bar style. |
GITPROMPT_SHORT_BELOW=<n> (env) |
unset | When set to a positive integer, the interactive prompt auto-flips to GITPROMPT_SHORT style whenever COLUMNS is below <n>. Live on resize via SIGWINCH. |
COLUMNS (env) |
(bash-managed) | Read by both the interactive GITPROMPT_SHORT_BELOW gate and the executed-mode width-aware cascade. Callers running gitprompt.bash as a statusline can override COLUMNS to budget the output independently of the terminal width. Falls back to stty size </dev/tty then no limit. |
GITPROMPT_HIDE_USERHOST=1 (env) |
unset | Omit the user@host segment. Evaluated once at source time. |
PROMPT_DIRTRIM (bash) |
3 |
Number of trailing path segments shown in cwd in full mode. gitprompt reads this variable but does not set it; long paths are trimmed with … (U+2026) rather than bash's native .... |
GITPROMPT_SELF_UPDATE=0 (env) |
unset | Disable the self-update nag and the hourly background git fetch. |
GITPROMPT_SELF_UPDATE_INTERVAL (env) |
3600 |
Seconds between background self-update fetches. |
git config gitprompt.selfUpdate false (per repo) |
unset | Per-repo opt-out from the self-update nag. |
git config gitprompt.forge <forge> (per repo) |
unset | Forge backing this repo's origin. Values: github (/tree/), gitlab (/-/tree/), gitea (/src/branch/; forgejo is a synonym). Picks the path layout for the upstream OSC 8 hyperlink. Set automatically by gitprompt autoconf. |
Keeping gitprompt up to date
Sourced gitprompt watches its own upstream. Once an hour (configurable
via GITPROMPT_SELF_UPDATE_INTERVAL) it forks a shallow git fetch in
the background; if the local branch is behind, the next prompt appends
a dim hint to the git segment:
… ⚑1 (gitprompt: 1 new — gitprompt update)
The background fetch is silent on every failure (network down, auth
needed, no upstream) — nothing prints, the hint just doesn't appear.
Opt out with GITPROMPT_SELF_UPDATE=0 or
git config gitprompt.selfUpdate false.
The companion gitprompt shell function:
| Subcommand | What it does |
|---|---|
gitprompt update |
Fast-forward (or hard-reset, in shallow clones) the gitprompt repo to @{u}. Refuses on detached HEAD or dirty working tree. |
gitprompt status |
Print resolved repo path, current branch, and behind-count. |
gitprompt doctor |
Print resolved knobs (env + git config) and Claude-Code-relevant environment for diagnosing config. |
gitprompt autoconf |
Detect which forge backs origin and write git config gitprompt.forge. See Forge detection. |
gitprompt man |
Render the embedded man page through man(1) on a tty; print raw nroff source otherwise. |
gitprompt help |
Usage. |
Extra arguments after update replace the default @{u} target, so
gitprompt update origin/main works. The remote is already current from the
background fetch, so git pull is skipped (and wouldn't work in a grafted
shallow clone anyway). The deploy clone is intended to track origin verbatim
— local-only commits there are not preserved when the shallow boundary
forces a hard reset; use a separate full clone for development. After a
successful update the existing mtime self-reload re-sources
gitprompt.bash at the next prompt — no shell restart needed.
Tab completion for gitprompt <subcommand> and the common git pull
flags after gitprompt update is registered automatically when the
script is sourced.
Forge detection
The upstream segment's OSC 8 hyperlink points at the branch page, not the project root, so the path layout has to match the forge software:
| Forge | Path |
|---|---|
| GitHub / GHE | /tree/ |
| GitLab | /-/tree/ |
| Gitea / Forgejo | /src/branch/ |
gitprompt picks the layout in this order:
git config gitprompt.forge(github/gitlab/gitea;forgejois a synonym ofgitea).- Otherwise a hostname heuristic:
github.com⇒ GitHub,gitlab.com⇒ GitLab, anything else ⇒ Gitea/Forgejo.
The hostname heuristic only catches the public instances. Self-hosted
GitHub Enterprise on git.corp.example.com will silently get the wrong
path. Run gitprompt autoconf once per repo to fix it:
$ gitprompt autoconf
gitprompt autoconf: github (probe (https://git.corp.example.com))
With curl available, autoconf probes
https://<host>/api/v1/version (Gitea/Forgejo) and the response
headers of / (GitLab via x-gitlab-meta, GitHub/GHE via
x-github-request-id). Without curl it falls back to the hostname
heuristic — same logic as the prompt path uses, just persisted to
git config so it survives renames and shows up in git config -l.
Flags:
--print— show the detected forge without writing.--unset— cleargitprompt.forge; the prompt falls back to the hostname heuristic on the fly.--no-probe— skipcurleven if installed (offline networks, captive portals).
Performance
Per prompt, in a normal repo, gitprompt issues ~6 git invocations
(branch, op, status, upstream, rev-list, stash, plus optional remote-url
and submodule).
On modern hardware in a small repo this is unnoticeable. In a huge repo
the git status call dominates; gitprompt times it out at
GITPROMPT_TIMEOUT and remembers the repo as "slow" for the rest of the
session, after which only branch / sync / stash are shown. Set
GITPROMPT_FAST=1 or git config gitprompt.fast true to skip the
probe entirely.
If you need a truly fast prompt in massive repos and don't mind a background daemon, use gitstatus instead — it's the right tool for that job.
Files
gitprompt.bash— the prompt itself; sourced as a library, executed as a Claude Code statusline (auto-detected).assets/hero.svg— the picture above
Developing
This repo wires its own Claude Code
status line to gitprompt.bash (in executed mode) via
.claude/settings.json, so opening the project in Claude Code gives
you a live, colored preview of the current rendering at the bottom of
the window — every refresh re-runs the script, so edits show up
immediately without a fresh shell.
gitprompt.bash also self-reloads in interactive shells: every prompt
checks the file's mtime, and if it changed since the last source,
the file is re-sourced before the prompt is built. Edit, save, hit
Enter — no . ~/.bashrc needed.
The self-update nag debounces background fetches via a stamp file at
<git-dir>/gitprompt-self-fetch.stamp inside the gitprompt repo.
gitprompt update removes the stamp after pulling so the next prompt
re-evaluates the behind-count immediately rather than waiting for the
hourly interval.
To opt out of the preview for your local checkout, drop a
.claude/settings.local.json (gitignored) that overrides statusLine,
or remove the project from Claude Code's allowed paths.
License
Apache License, Version 2.0. See LICENSE.