Open-Source Wikis

/

Starship

/

Features

/

Prompt rendering

starship/starship

Prompt rendering

The end-to-end flow that turns a starship prompt invocation into a styled string the shell prints. This is the most important code path — it runs on every redraw — and its performance profile shapes a lot of the codebase.

End-to-end timeline

sequenceDiagram
    participant Shell
    participant Main as src/main.rs
    participant Print as src/print.rs
    participant Context
    participant Modules as src/modules/*
    participant Stdout

    Shell->>Main: starship prompt --status=$? --jobs=$# ...
    Main->>Main: Cli::try_parse() (clap)
    Main->>Main: logger::init(); init_global_threadpool()
    Main->>Print: print::prompt(properties, target)
    Print->>Context: Context::new
    Note over Context: read $STARSHIP_SHELL,<br/>parse starship.toml,<br/>canonicalize cwd
    Print->>Print: load_formatter_and_modules
    Note over Print: parse format string,<br/>collect $variable set
    Print->>Modules: par_iter() over enabled modules
    Modules->>Context: try_begin_scan / get_repo / exec_cmd
    Modules-->>Print: Vec<Segment> per module
    Print->>Print: AnsiStrings (collapse redundant escapes)
    Print->>Print: wrap_colorseq_for_shell (PS1 markers)
    Print->>Stdout: write!

Step by step

  1. Parse CLI args. clap parses the subcommand (almost always prompt) and the Properties flags from the init script.
  2. Initialize the logger and rayon pool. logger::init() reads STARSHIP_LOG. init_global_threadpool() builds a pool with min(cpus, 8) workers (override via STARSHIP_NUM_THREADS). A separate rayon::spawn task cleans up old log files in the background.
  3. Build the Context. Context::new(args, target) reads $STARSHIP_SHELL, resolves the working directory (--pathcurrent_dir()$PWD--logical-path), and parses ~/.config/starship.toml. The repo and dir-contents lazy caches are not yet populated.
  4. Pick the format string. load_formatter_and_modules chooses among:
    • Target::Mainformat
    • Target::Rightright_format
    • Target::Continuationcontinuation_prompt
    • Target::Profile(name) → user-defined [profiles] <name> first, then internal_profiles[<name>] (e.g. the built-in claude-code profile)
  5. Parse the format string with StringFormatter::new, then ask VariableHolder::get_variables for the set of $variable names referenced.
  6. Run modules in parallel. For the special $all variable, all_modules_uniq(&modules) returns all PROMPT_ORDER entries not already in the format. For each variable, handle_module either:
    • Looks up the named module in ALL_MODULES and dispatches via modules::handle,
    • Or expands custom/env_var to the configured user-defined entries. Modules dispatched via $all run under par_iter().flat_map. Per-module timing is recorded in Module.duration.
  7. Assemble segments. Module::set_segments(...) populates each module; the synthetic root module aggregates them; ansi_strings_for_width(Some(width)) emits a Vec<AnsiString>.
  8. Strip and wrap escapes. nu_ansi_term::AnsiStrings(&segments).to_string() collapses adjacent escape codes; wrap_colorseq_for_shell rewrites them into shell-specific zero-width markers (\[\] for bash, %{%} for zsh, etc.) so the shell's prompt-width calculation is correct.
  9. Apply target-specific tweaks. Right prompts strip newlines. Tcsh has ! and \n quirks that get patched. Fish gets a leading \x1b[J to clear the screen (workaround for fish bugs #739, #279).
  10. Write to stdout. The writer is io::stdout().lock() for atomicity.

Performance levers

  • Module skipping: a module that is not referenced by format (or right_format / the chosen profile / $all minus disabled modules) does not run at all.
  • Lazy caches on Context mean only the first git/dir-contents access pays the cost.
  • Parallelism: rayon's pool runs disjoint modules concurrently.
  • Timeouts: scan_timeout (30ms default) bounds the directory scan; command_timeout (500ms default) bounds every Context::exec_cmd.
  • Selective gix features: the Cargo.toml comment links to issue #4251 explaining how default features were trimmed for prompt latency.

The right prompt

starship prompt --right selects Target::Right, which uses right_format instead of format. After rendering, the renderer strips newlines (buf = buf.replace('\n', "")) because most shells do not allow multi-line right prompts. The shell-side wiring that draws the right prompt is shell-specific — see Init scripts.

The continuation prompt

starship prompt --continuation uses continuation_prompt from StarshipRootConfig, defaults to [∙](bright-black) . It bypasses the standard format-string path and goes through a one-line StringFormatter invocation.

The profile prompt

starship prompt --profile <name> uses Target::Profile(name). Both user [profiles] and internal_profiles are searched. Used for the Claude Code statusline today; users can register their own (e.g., a transient prompt).

Failure modes

  • Bad format string: parse error is logged at error; the prompt falls back to literal >.
  • Bad TOML: parse error is logged at error; the entire config is treated as missing and defaults are used.
  • Module panic (very rare; modules are written to never panic): Rust's default panic behavior kicks in. The shell sees an empty prompt and starship exits with a backtrace if RUST_BACKTRACE is set.
  • TERM=dumb: The renderer prints Starship disabled due to TERM=dumb > and skips everything. See get_prompt.

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

Prompt rendering – Starship wiki | Factory