starship/starship
Architecture
Starship is structured as a single Rust binary that the user's shell invokes once per prompt redraw. The binary's job is to read configuration, build a Context, fan out across the configured modules in parallel, and write an ANSI-colored string to stdout.
High-level flow
graph TD
Shell["User's shell<br/>(bash/zsh/fish/...)"] -->|"calls starship init <shell>"| Init["src/init/mod.rs<br/>StarshipPath + init_stub"]
Init -->|prints small bootstrap| Shell
Shell -->|"on each redraw: starship prompt"| Main["src/main.rs<br/>Cli (clap derive)"]
Main --> Print["src/print.rs<br/>get_prompt"]
Print --> Context["src/context.rs<br/>Context::new"]
Context --> Config["src/config.rs<br/>StarshipConfig (TOML)"]
Print --> Formatter["src/formatter/<br/>StringFormatter (pest)"]
Formatter -->|"$module variables"| Modules["src/modules/<name>.rs<br/>(rayon parallel)"]
Modules --> Segments["src/segment.rs<br/>Segment + AnsiString"]
Segments -->|"AnsiStrings()"| Stdout["stdout"]Every box maps to a file or directory in the repository. The pieces are described in detail in the subsystems section.
Subcommands
starship is a multi-command binary defined in src/main.rs. The shell's init snippet only ever calls a few of these on a hot path; the rest are user-facing utilities.
| Subcommand | Purpose | Hot path? |
|---|---|---|
init <shell> |
Print the bootstrap snippet for the named shell | once per shell session |
init <shell> --print-full-init |
Print the full init script | once per shell session |
prompt (with --right, --profile, --continuation) |
Print the actual prompt | yes (every redraw) |
module <name> |
Print a single module's output | rarely, for debugging |
explain |
Annotate the current prompt with module names and descriptions | no |
timings |
Show per-module compute time | no |
config / print-config / toggle |
Edit or inspect starship.toml |
no |
preset <name> |
Print a packaged preset (docs/public/presets/toml/) |
no |
bug-report |
Open a GitHub issue with environment info | no |
completions <shell> |
Generate shell completions for the starship binary itself |
no |
session |
Print a 16-character random alphanumeric session id | yes (some shells) |
time |
Print epoch milliseconds (used by cmd_duration) |
yes |
statusline claude-code |
Read Claude Code JSON from stdin and print a Claude statusline | only for Claude Code |
config-schema |
Emit the JSON Schema for starship.toml (feature config-schema) |
no |
The two-phase init
Each shell needs a tiny snippet to wire starship up. To keep that snippet short, Starship uses a two-phase init:
- The user adds something like
eval "$(starship init bash)"to their shell rc file. starship init bashprints a short stub (the contents ofsrc/init/starship.bash's stub block, but most shells use a single line that re-invokesstarship init bash --print-full-init).- The stub then sources the full init script, which is
src/init/starship.<shell>shipped at compile time viainclude_str!.
The full init script defines a precmd/preexec hook (or shell-specific equivalent) that runs starship prompt on every redraw and substitutes its output into PS1/PROMPT/etc. See the init scripts subsystem for the per-shell quirks.
Prompt rendering pipeline
When the shell's hook fires starship prompt, the binary walks through these stages:
sequenceDiagram
participant Shell
participant Main as src/main.rs
participant Print as src/print.rs
participant Context as src/context.rs
participant Config as src/config.rs
participant Formatter as src/formatter
participant Modules as src/modules/*
participant Stdout as stdout
Shell->>Main: starship prompt --status=$? --jobs=$#
Main->>Print: print::prompt(args, Target::Main)
Print->>Context: Context::new(args, target)
Context->>Config: StarshipConfig::initialize()
Config-->>Context: parsed TOML
Print->>Formatter: StringFormatter::new(format)
Formatter-->>Print: VariableHolder (set of $module names)
Print->>Modules: par_iter() over enabled modules
Modules-->>Print: Vec<Segment> per module
Print->>Print: AnsiStrings + shell wrapping
Print->>Stdout: write!(stdout, prompt)The key idea is that the format string drives the work. Starship parses format (and right_format, or one of the [profiles]) with the pest grammar in src/formatter/spec.pest, extracts the set of $module variables it references, and only computes those modules. Modules referenced by the special $all variable are computed if they are not disabled. This is why Starship stays fast even with a giant config — unused modules never run.
Parallelism model
A global rayon thread pool is initialized in init_global_threadpool inside src/main.rs. By default it is sized at min(num_cpus, 8), but the user can override it with the STARSHIP_NUM_THREADS environment variable (see num_rayon_threads in src/lib.rs).
Inside get_prompt, modules referenced by $all are dispatched with par_iter().flat_map(...). Each module runs modules::handle(name, context) which dispatches to the right module(context) function in src/modules/<name>.rs. Modules read the shared Context (which uses interior OnceLock and OnceCell caches for things like the directory listing and the parsed git repo), so they can share computed state without locking.
Configuration layers
Starship merges several layers into a final config that modules see:
- Built-in defaults, encoded in each module's
<Name>Config::default()impl insrc/configs/<name>.rs. starship.tomlat$STARSHIP_CONFIG, or~/.config/starship.toml, parsed byStarshipConfig::initialize()insrc/config.rs.- Module config getters, exposed by
Context::new_module(name)andModule.config, which let<Name>Config::try_load(module.config)deserialize a partial table on top of defaults. - Inline
disabled/formatoverrides that the user can set viastarship config <key> <value>(seesrc/configure.rs).
The full default-merged config can be inspected with starship print-config --default.
Output: segments, modules, ANSI strings
The smallest unit of output is a Segment — typed text, optional fill, or a line terminator. Each module returns a Module containing Vec<Segment>. Modules are composed inside a synthetic root module (Module::new("Starship Root", ...)) and finally rendered with nu_ansi_term::AnsiStrings so duplicate ANSI codes are stripped.
After rendering, the output goes through wrap_colorseq_for_shell in src/utils/mod.rs, which wraps non-printing escapes in shell-specific markers (\[\] for bash, %{%} for zsh, …) so the shell computes prompt width correctly.
What runs once vs every prompt
| Runs once per session | Runs every prompt |
|---|---|
starship init <shell> (and the cached full init script) |
starship prompt (left + right + continuation) |
| Shell's first hook installation | starship time (for cmd_duration, on shells that need it) |
starship session (some shells use it for STARSHIP_SESSION_KEY) |
|
starship statusline claude-code (only inside Claude Code) |
This split matters for performance work: the only path that needs to be fast is starship prompt. Everything else can take its time.
Where to read next
- Modules — what modules exist and how they work.
- Context — the shared object that modules read.
- Configuration — how
starship.tomlbecomes typed structs. - Format string DSL — the pest grammar and
StringFormatter. - Init scripts — the shell-specific glue.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.