starship/starship
Context
The Context is the per-prompt object passed to every module. It owns the parsed configuration, the current and logical working directories, the user's environment, lazy caches for the directory contents and Git repo, the active shell, the render target, and (for the Claude Code statusline) Claude session data. Source: src/context.rs.
Shape
pub struct Context<'a> {
pub config: StarshipConfig,
pub current_dir: PathBuf,
pub logical_dir: PathBuf,
dir_contents: OnceLock<Result<DirContents, std::io::Error>>,
pub properties: Properties,
repo: OnceLock<Result<Repo, Box<gix::discover::Error>>>,
pub shell: Shell,
pub target: Target,
pub width: usize,
pub env: Env<'a>,
#[cfg(test)]
pub cmd: HashMap<&'a str, Option<CommandOutput>>,
#[cfg(test)]
pub root_dir: tempfile::TempDir,
#[cfg(feature = "battery")]
pub battery_info_provider: &'a (dyn BatteryInfoProvider + Send + Sync),
pub root_config: StarshipRootConfig,
pub claude_code_data: Option<Box<ClaudeCodeData>>,
_marker: PhantomData<&'a ()>,
}The lifetime 'a ties Context to anything it borrows from the environment (notably the Env<'a> for env-var mocking).
Construction
Context::new(properties, target) is called once per starship prompt invocation. It:
- Detects the shell from
$STARSHIP_SHELL. - Resolves the current directory via
arguments.path→env::current_dir()→$PWD→arguments.logical_path. - Resolves the logical directory the same way, but starting from
--logical-path. - Calls
Context::new_with_shell_and_path, which parsesstarship.tomlviaStarshipConfig::initialize, normalizespipestatusandstatus_code, canonicalizescurrent_dir(withduncefor Windows), and returns the populatedContext.
StarshipRootConfig::load (or default() if no config) is materialized eagerly because every module reads the root config (e.g. command_timeout, scan_timeout, palette).
Properties
Properties is the clap-parsed bag of CLI flags Starship's init scripts pass on every redraw:
| Field | CLI flag | Where it comes from |
|---|---|---|
path: Option<PathBuf> |
--path |
Some shells (e.g. Powershell on UNC paths) need to override cwd |
logical_path: Option<PathBuf> |
--logical-path |
"the path the user typed", separate from the canonicalized one |
status_code: Option<String> |
--status |
$? |
pipestatus: Option<Vec<String>> |
--pipestatus |
${PIPESTATUS[*]} (bash) / $pipestatus (zsh) / $pipestatus (fish) |
cmd_duration: Option<String> |
--cmd-duration |
Milliseconds the previous command took |
keymap: Option<String> |
--keymap |
vi-mode for character |
jobs: Option<u64> |
--jobs |
Number of background jobs |
terminal_width: usize |
--terminal-width |
$COLUMNS, or 0 if unknown |
shlvl: Option<i64> |
--shlvl |
$SHLVL |
Defined in Properties using clap::Parser.
Lazy state
Two fields are wrapped in OnceLock so they pay their cost only once per prompt:
dir_contents— populated on the first call toContext::dir_contents()ortry_begin_scan(). Reads the directory once into aDirContents(aHashSetof files, file names, folders, and extensions). Time-bounded byscan_timeout(default 30ms) and uses a worker thread +mpsc::recv_timeoutto abort runaway reads.repo— populated on the first call toContext::get_repo(). Discovers the git directory viagix, opens it with custom permissions (config reading enabled even for "reduced" trust), and packages the result into aRepostruct.
This is what lets multiple modules look at the same directory without rescanning, and lets git_status/git_metrics/git_branch share the same gix handle.
Environment access
Modules call context.get_env("FOO") and context.get_env_os("FOO") rather than std::env::var. Why? Because in tests the underlying Env<'a> (defined in src/utils/env.rs) is a HashMap rather than a real OS environment, and ModuleRenderer::env injects fixtures into it. The wrapper enforces that test runs are deterministic.
context.get_home() similarly checks for $HOME first when cfg(test), then falls back to dirs::home_dir.
Process execution
context.exec_cmd(cmd, args) is the canonical way to run an external command from inside a module. It:
- Looks up
(cmd, args)inContext::cmd(test-only), returning a mockedCommandOutputif present. - Falls back to
crate::utils::mock_cmd(also test-only), which encodes "well-known" command outputs for things likenode --version. The matched commands are listed inmock_cmdinsrc/utils/mod.rs; the Contributing guide warns that adding a new exec call requires a matching mock. - Otherwise spawns a real process via
crate::utils::create_command(cmd)(which sets predictable defaults), runs it inside the current dir, and bounds it bycommand_timeout(default 500ms) usingprocess_control::Control::time_limit.
context.exec_cmds_return_first lets a module try a list of binaries (e.g., python then python3) and pick the first one that responds.
Detection helpers
context.try_begin_scan()? // ScanDir; sets ./files/extensions/folders to match
.set_files(&["Cargo.toml"])
.set_extensions(&["rs"])
.set_folders(&["src"])
.is_match();
context.begin_ancestor_scan() // ScanAncestors; walks upward from cwd
.set_files(&[".hg"])
.scan();
context.detect_env_vars(&["VIRTUAL_ENV", "!CONDA_DEFAULT_ENV"]) // ! = negationScanDir operates on the cached DirContents. ScanAncestors walks current_dir.ancestors() and stops when it finds a match — used by the hg modules to find a repo root.
The Repo cache
Context::get_repo returns a &Repo containing:
repo: ThreadSafeRepository— gix repo handle for thread-local conversion.branch: Option<String>— short branch name (orHEADwhen detached).workdir: Option<PathBuf>—Nonefor bare repos.path: PathBuf— the.gitdirectory.state: Option<gix::state::InProgress>— rebase / merge / etc.remote: Option<Remote>—nameandbranchof the upstream.fs_monitor_value_is_true: bool— read from the repo'score.fsmonitorto enable optimizations ingit_status.
get_current_branch (free function in src/context.rs) handles symbolic refs and detached-HEAD shortenings.
Render targets
pub enum Target {
Main,
Right,
Continuation,
Profile(String),
}Selected in src/main.rs from the combination of --right, --continuation, and --profile=<name>. Target::Profile looks up the named profile in user_profiles (from [profiles] in TOML) first, falling back to internal_profiles (the default_profiles() shipped in code). The Claude Code statusline uses Target::Profile("claude-code").
Shells
pub enum Shell {
Bash, Fish, Ion, PowerShell, Pwsh, Zsh, Elvish, Tcsh, Nu, Xonsh, Cmd, Unknown,
}Detected from $STARSHIP_SHELL, set by the init scripts. A few modules (character, shell, cmd_duration's init flow) branch on shell to handle quirks (e.g. fish's default keymap meaning vi-normal).
Tests
Tests construct Context via default_context() in src/test/mod.rs, which stamps a fresh tempfile::TempDir as root_dir. Helpers like context_path rewrite "absolute" paths (/etc/foo) into paths under that tempdir so tests cannot leak onto the host filesystem.
Entry points for modification
- New shell support: add a
Shellvariant + dispatch inContext::get_shelland update the matching init script undersrc/init/. - New
Propertiesflag (e.g. a new shell concept that needs to flow into modules): add a field toProperties, plumb it throughContext, and update every init script that should set it. - New repo metadata exposed to git modules: add a field to
Repoinsrc/context.rsand populate it during theOnceLockinit inContext::get_repo. - New context-wide cache: wrap your data in
OnceLock<Result<...>>, add a getter that callsget_or_init, and document the failure mode.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.