Data and lifecycle
Reference the complete NBSP_DATA schema, asynchronous update lifecycle, safe prompt escaping, and refresh behavior.
Update lifecycle
At every precmd, detached mode asks nbsp data --format nul for a framed
snapshot. It replaces the global NBSP_DATA associative array only after the
producer exits successfully and all 18 required schema fields, the frame
terminator, and schema version have been validated. A completed background Git
worker can request another load and a ZLE redraw.
Register prompt functions in nbsp_data_update_functions:
my_prompt_update() {
# Assign PROMPT and/or RPROMPT here.
}
nbsp_data_update_functions+=(my_prompt_update)
my_prompt_update
Callbacks run after every successful complete-frame parse, even when the new
values equal the previous values. This includes a reload of the preserved old
cache after a Git refresh fails. A malformed, incomplete, wrong-version, or
failed frame leaves the previous NBSP_DATA untouched and runs no callbacks.
Callback output is not hidden or redirected.
Data schema
| Key | Meaning |
|---|---|
schema_version |
Data contract version, currently 2 |
cwd |
Full working directory |
path |
Abbreviated display path |
status |
Previous command’s exact exit status |
duration_ms |
Previous command duration in milliseconds |
jobs |
Background job count |
node_version |
A 1-255-byte ASCII label derived from the final NVM_BIN component (or its parent when a final bin has a preceding component), with one leading v removed before a digit; otherwise empty |
git_present |
1 when native repository discovery succeeds |
git_valid |
1 when a valid cache snapshot’s branch matches the branch read from current Git metadata |
git_branch |
Branch name or short detached commit |
git_updated_ms |
Snapshot wall-clock timestamp in milliseconds when git_valid=1; otherwise 0 |
git_staged |
Porcelain records with staged changes |
git_modified |
Porcelain records with worktree changes |
git_untracked |
Untracked porcelain records |
git_conflicted |
Unmerged porcelain records |
git_ahead |
Commits ahead of upstream |
git_behind |
Commits behind upstream |
git_stashes |
Stash count |
Consumers should require a schema version they understand, address records by key rather than position, and ignore unknown keys added by a compatible future version.
Repository refresh state
On a prompt in a repository, git_present may be 1 while git_valid is 0.
When current Git metadata can be read, git_branch uses that branch or short
detached commit and detailed counters remain zero until a branch-matched cache
snapshot is available. If current branch metadata cannot be read, a cached
branch label may be exposed for context, but git_valid remains 0.
A successful refresh collects one porcelain-v2 status, rereads current branch
metadata, and publishes only if both branch values still match. The next
successful data-frame load can then expose git_valid=1. No particular prompt
is guaranteed to be the first successful refresh: lock contention, timeout,
Git failure, a branch change, or cache publication failure can defer it.
Use git_valid when your presentation needs to distinguish a confirmed zero
from counters that have not arrived:
if [[ ${NBSP_DATA[git_valid]} == 0 ]]; then
git_state=" ..."
fi
Snapshot boundaries
Each NBSP_DATA value is a point-in-time observation, not a live filesystem
view. Files, branches, jobs, and environment state can change immediately after
collection. Detailed Git counters come from the most recent branch-coherent
cache snapshot; git_updated_ms identifies when that snapshot was collected.
precmd performs the regular foreground load and schedules a background
refresh. If a refresh was already running when a foreground command began, the
integration schedules a forced post-command refresh after that worker finishes.
Changing directory stops tracking the old worker’s notification, and a
completion is applied only while the shell is still in the directory from which
that refresh was started. The detached worker can still finish its cache work.
These checks prevent a known old-directory result from being applied to the new
prompt, but they cannot turn the snapshot into a continuously live view.
Safe prompt text
Zsh prompt options change which bytes are interpreted during prompt expansion. Quote dynamic values before inserting them:
nbsp_prompt_quote "${NBSP_DATA[path]}"
local safe_path=$REPLY
nbsp_prompt_quote:
- writes the quoted value to
REPLY; - replaces control bytes with
?; - doubles
%whenPROMPT_PERCENTis enabled; - encodes the complete value as one non-recursive Zsh parameter expression
when
PROMPT_SUBSTis enabled, so$, backticks, backslashes, quotes, and other shell syntax remain data; - doubles
!whenPROMPT_BANGis enabled; - does not start a process.
The helper reads but never changes the current option state. Quote and display
the value under the same PROMPT_PERCENT, PROMPT_SUBST, and PROMPT_BANG
settings; call it again after changing any of those options. Do not insert raw
fields into PROMPT, RPROMPT, or a PROMPT_SUBST expression.
Prompt substitution
Callbacks can place quoted values into the prompt string while PROMPT_SUBST
performs final expansion on each redraw:
setopt promptsubst
prepare_prompt_data() {
local path branch=
nbsp_prompt_quote "${NBSP_DATA[path]}"
path=$REPLY
if [[ -n ${NBSP_DATA[git_branch]} ]]; then
nbsp_prompt_quote "${NBSP_DATA[git_branch]}"
branch=" [$REPLY]"
fi
PROMPT="${path}${branch} %# "
}
nbsp_data_update_functions+=(prepare_prompt_data)
prepare_prompt_data
Assign the helper result into the actual prompt string in the callback, as above. Hiding that result behind another parameter reference would add an extra expansion level; Zsh deliberately does not recursively rescan parameter results as new prompt source.
OSC sequences
Detached mode leaves terminal control sequences under your ownership. It
updates NBSP_DATA and requests a redraw, but does not replace or interpret
OSC wrappers in your prompt.
See Prompt recipes for a complete terminal-title example.