Open-Source Wikis

/

Starship

/

Starship

/

Architecture

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 &lt;shell&gt;"| 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:

  1. The user adds something like eval "$(starship init bash)" to their shell rc file.
  2. starship init bash prints a short stub (the contents of src/init/starship.bash's stub block, but most shells use a single line that re-invokes starship init bash --print-full-init).
  3. The stub then sources the full init script, which is src/init/starship.<shell> shipped at compile time via include_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:

  1. Built-in defaults, encoded in each module's <Name>Config::default() impl in src/configs/<name>.rs.
  2. starship.toml at $STARSHIP_CONFIG, or ~/.config/starship.toml, parsed by StarshipConfig::initialize() in src/config.rs.
  3. Module config getters, exposed by Context::new_module(name) and Module.config, which let <Name>Config::try_load(module.config) deserialize a partial table on top of defaults.
  4. Inline disabled/format overrides that the user can set via starship config <key> <value> (see src/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.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Architecture – Starship wiki | Factory