Shell Startup Files
September 1, 2026 ยท View on GitHub
Base integrates with Bash and Zsh through small managed sections in the user's real dotfiles. Base does not take over whole dotfiles.
The command that installs or refreshes those sections is:
basectl update-profile
By default it updates all four startup files:
~/.bash_profile~/.bashrc~/.zprofile~/.zshrc
Missing files are created. Existing files keep their non-Base content; Base only
adds or replaces its marked section. Before changing an existing startup file,
basectl update-profile writes a timestamped sibling backup such as
~/.bashrc.backup.YYYYMMDDTHHMMSS.
To remove Base from shell startup files without hand-editing dotfiles, run:
basectl update-profile --remove
This removes only Base-managed marked sections from Bash and Zsh startup files.
Use basectl update-profile --remove --dry-run to preview the planned backups
and removals.
basectl update-profile also creates ~/.base.d/profile.conf, which records
whether the user has opted into Base's optional shell defaults. The managed
dotfile sections stay minimal and defer PATH/default handling to the sourced
Base snippets. The same sourced snippets also register basectl shell
completions, so future completion improvements arrive when Base is updated
without rewriting user dotfiles.
Run basectl update-profile --defaults to enable those optional defaults, and
run basectl update-profile --no-defaults to disable them again. Plain
basectl update-profile preserves the existing preference.
BASE_PROFILE_VERSION records the schema version of this Base-managed file. It
is reserved for future migrations and is not intended to be edited by users.
Update Base or a Base-managed project checkout with:
basectl update
basectl update bankbuddy
Omitting the project is equivalent to basectl update base. This command is
intentionally conservative. In a source checkout, it only runs from the selected
project repository's default branch, requires tracked project files to be clean,
pulls the latest changes through Git, and runs basectl setup <project> only
when the pull changes the checked-out revision. Untracked files do not block the
update; Git still stops the pull if an incoming tracked file would overwrite
them.
In a Homebrew-managed install, the Base update path remains Base-only:
basectl update runs the Base package upgrade,
brew upgrade basefoundry/base/base, and runs basectl setup base with
inherited Base environment variables cleared only when the installed Base version
changes. basectl update --dry-run prints the Git or Homebrew handoff it would
perform without changing files or packages.
For manual Homebrew upgrades outside basectl update, prefer
brew upgrade --no-ask basefoundry/base/base so Homebrew skips the preview
prompt path on already-current installs. If Homebrew refuses to load
basefoundry/base/base-bash-libs from an untrusted tap, run
brew trust basefoundry/base once and retry the upgrade.
Base also reads ~/.baserc when it exists. Unlike profile.conf, ~/.baserc
is user-managed and may be hand-edited. It is intended for simple,
shell-startup-safe Base preferences such as BASE_DEBUG=1; it should not become
a second .bashrc with arbitrary setup logic. See
Runtime Environment for the full variable
contract and mutability policy.
~/.baserc must not set Base-owned runtime or profile variables such as
BASE_HOME, BASE_BIN_DIR, BASE_LIB_DIR, BASE_BASH_LIBS_DIR,
BASE_BASH_LIBS_SOURCE, BASE_OS, BASE_PLATFORM, BASE_HOST_ENV, BASE_SHELL,
BASE_PLATFORM_TOOLS_HOME, BASE_PLATFORM_TOOLS_BIN_DIR,
BASE_PROFILE_VERSION, BASE_ENABLE_BASH_DEFAULTS, or
BASE_ENABLE_ZSH_DEFAULTS. Base startup snippets reject and restore those
variables if ~/.baserc tries to change them.
Base-managed sections use explicit markers such as:
# >>> base: bashrc managed >>>
# ... Base-managed content ...
# <<< base: bashrc managed <<<
Base Snippets
The managed sections source matching snippets under lib/shell/:
lib/shell/bash_profilefor~/.bash_profilelib/shell/bashrcfor~/.bashrclib/shell/zprofilefor~/.zprofilelib/shell/zshrcfor~/.zshrc
The names intentionally mirror the dotfiles they support, without leading dots inside the repository.
Bash snippets and the Bash runtime rcfile share lib/shell/baserc_guard.sh for
safe ~/.baserc loading. Zsh snippets keep their own guard logic for now.
Login Profiles
bash_profile and zprofile stay thin.
For Bash, Base makes the login-shell bridge explicit: the Bash profile snippet
sources ~/.bashrc with a guardrail. Bash needs this because login Bash shells
do not automatically read ~/.bashrc.
For Zsh, Base does not source ~/.zshrc from zprofile. Zsh already reads
~/.zshrc for interactive shells.
Interactive RC Files
bashrc and zshrc are where interactive shell behavior belongs.
They are responsible for:
- guarding against non-interactive execution
- guarding against repeated sourcing
- deriving and exporting
BASE_HOMEfrom the sourced Base snippet - adding Base's
bin/directory toPATHsobasectlis available after login - adding an optional sibling
base-platform-tools/bindirectory toPATHwhen that repo is present - keeping dotfile integration separate from the full Base runtime bootstrap
- optionally enabling shared shell defaults when
basectl update-profile --defaultsis used
They do not source base_init.sh. Base runtime setup happens only when the
basectl command runs a Base command, runs an explicit script path, or starts a
Base-enabled Bash shell.
When basectl activate <project> starts an interactive Bash runtime shell, it
uses Base's runtime rcfile rather than making Bash read ~/.bashrc directly.
That runtime rcfile loads base_init.sh, sources the user's ~/.bashrc once
with guardrails, activates the project virtual environment, and finally sets the
Base runtime prompt. This keeps user aliases and normal interactive Bash
behavior available while making Base stdlib functions such as import_base_lib
available during user Bash startup.
Debugging Shell Startup
Set BASE_DEBUG=1 to make Base-managed shell startup snippets print diagnostic
messages while they run. This is intentionally independent of base_init.sh and
stdlib logging, because dotfile debugging can happen before the Base runtime is
loaded.
For normal terminal startup, put this in ~/.baserc:
BASE_DEBUG=1
For one-off checks, use an environment variable:
BASE_DEBUG=1 bash --rcfile ~/.bashrc -i
BASE_DEBUG=1 zsh -i
BASE_DEBUG=1 basectl
Diagnostics are printed to stderr and show which Base snippet loaded, how
BASE_HOME was derived, whether $BASE_HOME/bin was added to PATH, whether
optional shell defaults were enabled, and how the Base runtime shell was layered.
For command debugging, basectl -v <command> enables DEBUG logs after the Base
runtime is loaded and the selected command is dispatched. For earlier startup
debugging, use wrapper options that are consumed by bin/basectl before
base_init.sh is sourced:
--debug-wrapperenablesLOG_DEBUG=1before runtime initialization, including DEBUG diagnostics from reusable Bash libraries.--utc-wrapperenables UTC log timestamps before runtime initialization.--colorpreserves color-aware wrapper argument handling while keeping the flag out of command arguments.
Prefer -v unless the problem happens before the command implementation starts.
Standard Shell Defaults
Base can provide optional, opinionated shell defaults, but they are not enabled
by plain basectl update-profile.
Current default-setting scripts are:
lib/shell/base_defaults.shfor shell-neutral defaults shared by Bash and Zshlib/shell/bash_defaults.shfor Bash-specific defaultslib/shell/zsh_defaults.shfor Zsh-specific defaults
Users can opt in during profile updates with:
basectl update-profile --defaults
Users can opt out again with:
basectl update-profile --no-defaults
Those defaults are intended to stay conservative:
- aliases like
rm -i,cp -i,mv -i - vi-style command editing
- completion ergonomics
- editor defaults
- pager defaults
- prompt defaults
- terminal usability behavior
- history behavior, including duplicate suppression, timestamped history, and multi-line command preservation
The defaults should not become a personal dotfile bundle. The following remain
outside basectl update-profile --defaults unless Base adds a separate,
explicit opt-in:
- color or listing aliases for tools such as
ls,grep, ordiff - navigation shortcuts,
CDPATH, auto-directory changes, or spelling correction - signing and agent helpers such as
GPG_TTY - strict shell modes such as global
errexit,nounset, orpipefail - prompt features that run expensive checks, such as dirty Git status
Those settings are platform-sensitive, workflow-specific, or more likely to
change command behavior in surprising ways. Keep personal aliases and functions
in normal shell dotfiles, and keep simple Base preferences in ~/.baserc.