Reference for .toolbox.yaml: every supported key, the loading order, and the TOOLBOX_* environment overrides. Source of truth: internal/config/config.go (schema + validation) and internal/configexample/render.go (annotated template).
toolbox init # write an annotated .toolbox.yaml in the current directory (--force to overwrite)
toolbox config example # print the same annotated template to stdout
toolbox config show # print the fully-resolved configuration (--origin annotates each key's layer)
toolbox config doctor # validate without modifying
toolbox config ui # interactively view/edit keys across the global & repo layers (needs a TTY)
Prefer not to hand-edit YAML? toolbox config ui is an interactive, provenance-first editor for every key below — it shows both the effective value and what each layer (global / repo) sets, and writes through the same validated, comment-preserving path as config set. The full toolbox config subcommand tree is documented in commands.
Configuration is loaded from (highest priority first):
TOOLBOX_* environment variables — only the four env-bound keys (image, registry_mirror, pull, bridge); for those they override every file layer below. Every other TOOLBOX_* var is ignored (viper's AutomaticEnv binds only these keys).--config flag.toolbox.yaml walking up from the current working directory (search stops at $HOME or the filesystem root) — running toolbox shell from any subdirectory of a workspace still picks up that workspace's project config~/.toolbox.yaml (global)| Key | Type | Default | Purpose |
|---|---|---|---|
mounts |
list | built-in defaults | Patch / replace / append / disable the default bind mounts by name. |
mounts_root |
string | "" |
Retarget every ~/.toolbox/-managed default mount to a custom root. |
inherit_host_auth |
list | [] |
Opt listed CLIs into the host's real credential path instead of the isolated default. |
shells |
map | – | Named shell shortcuts: <name>: {path, env}. |
shell |
string | zsh |
Login shell inside the container (only zsh is supported). |
agent |
string | claude |
Default AI agent auto-launched by toolbox worktree: claude or codex. |
image |
string | "" |
Full image ref override (pull-source concern). |
registry_mirror |
string | "" |
Swap only the registry host of the canonical ref. |
pull |
string | auto |
Registry-sync policy: auto / always / never. |
sdd |
map | – | Per-repo Spec-Driven-Development skill packs (gsd, bmad, openspec). |
bridge |
bool | true |
Mount the host bridge state dir (browser / editor / proximo forwarding). |
browser_bridge |
bool | – | Deprecated alias of bridge. |
proximo |
bool | auto | .test reachability + CA trust; omitted = auto-detect (on iff proximo's CA exists on the host). |
managed_statusline |
bool | true |
Image-owned Claude Code statusline, re-applied every shell start; false keeps your own. |
env |
map | – | Arbitrary env vars injected into the in-container shell. |
worktree |
map | – | Tune toolbox worktree sessions; seed adds extra gitignored paths to carry into a new worktree. |
shellLogin shell inside the container. Only zsh is supported (the default); bash is rejected at config load with an explicit migration hint (config.ValidateShell).
agentDefault AI agent auto-launched by toolbox worktree sessions. Accepts claude or codex — the two agents baked into the canonical image (config.ValidateAgent). Resolved with precedence --agent flag > this key > the default claude, so the flag is optional once a default is set. Honouring the standard loading order, it can be set globally (~/.toolbox.yaml) for a per-user default or per-directory (.toolbox.yaml) to pin a project to one agent.
Set it via toolbox config set agent <value> (--where selects global vs local). The key has no default written to disk: when unset it resolves to claude at launch, and config show renders that resolved value (agent: claude). A non-canonical image: lacking the chosen agent fails at launch, not at validation.
managed_statuslineThe runtime image ships a curated Claude Code statusline and applies it to every container by force-setting ~/.claude/settings.json statusLine on each shell start (only that key is rewritten — everything else in your settings is preserved). It is image-owned policy: a local edit to the statusline is overwritten on the next shell, so change it via a PR to this repo, not in the container.

Set managed_statusline: false to opt out — the boot hook then leaves your own statusLine untouched. Default (omitted or true) is managed-on. Mechanics in shell-start internals.
inherit_host_auth: [<key>, …] in .toolbox.yaml opts the listed CLIs into reading the host's standard credential path instead of the isolated ~/.toolbox/<key>/ default. Default is [] — fully isolated, matches the pre-#276 behavior.
Eligible CLIs and their host paths (catalog entries with non-nil HostAuthMount):
| Key | Host path | Container path |
|---|---|---|
gh |
~/.config/gh |
/home/toolbox/.config/gh |
glab |
~/.config/glab-cli |
/home/toolbox/.config/glab-cli |
gcloud |
~/.config/gcloud |
/home/toolbox/.config/gcloud |
docker |
~/.docker |
/home/toolbox/.docker |
azure |
~/.azure |
/home/toolbox/.azure |
oci |
~/.oci |
/home/toolbox/.oci |
claude |
~/.claude |
/home/toolbox/.claude |
codex |
~/.codex |
/home/toolbox/.codex |
atuin |
~/.local/share/atuin |
/home/toolbox/.local/share/atuin |
Validation in config.Plan rejects unknown keys and keys whose catalog entry lacks HostAuthMount.
Mount semantics: when a key is listed in inherit_host_auth, the default ~/.toolbox/<key> mount is dropped (not supplemented) — two mounts at the same container target would shadow unpredictably. User mounts: patches keying on the same name: still compose on top of the inherited mount.
mounts_root interaction: if both mounts_root: /custom and inherit_host_auth: [<key>] are set, the mounts_root retargeting is bypassed for that key — host inheritance pulls from the host's canonical path (e.g. ~/.config/gh), not from /custom/gh. mounts_root still applies to every other default mount. If you need the credential dir on an encrypted volume, choose one approach or the other.
Pre-stat check: inherit_host_auth: [<key>] requires the host source path to exist at config-load time. If it does not, toolbox shell fails with a clear error pointing at the missing path — silent soft-skip would have left the container with no credential mount at all (worse than failing loud).
Read-write inheritance: inherited mounts are read-write. Most listed CLIs refresh tokens or update session state during normal use (atuin appends history, claude/codex write session state, gh/docker rotate OAuth refresh tokens) — RO would EROFS those writes. You opt in explicitly: your host credential dir is now writable by container processes.
macOS keychain caveat: gh on macOS stores its OAuth token in the system keychain by default — ~/.config/gh/hosts.yml carries the account but no oauth_token, so inheriting that dir mounts a token-less config and gh auth status inside the container reports the token invalid. Workaround: re-login on the host with gh auth login --insecure-storage (persists the token into hosts.yml), or skip inheritance for gh and log in once inside the container (isolated ~/.toolbox/gh survives recreates). The same class of issue applies to any CLI that delegates secret storage to an OS keychain.
toolbox shell defaults to ghcr.io/filippolmt/toolbox:latest. There is no per-tool opt-out, no local-hash fallback, no auto-build branch. toolbox build is the explicit escape hatch — it overwrites the local cache of the canonical tag so the next toolbox shell picks up the freshly built image. Run docker pull ghcr.io/filippolmt/toolbox:latest to restore the upstream copy.
Source relocation (opt-in). The ref and pull behaviour are configurable — globally (~/.toolbox.yaml), per-repo (.toolbox.yaml), or via TOOLBOX_* env — for users who serve the image from a proxy hub / pull-through cache (Harbor, Artifactory, Nexus, ECR pull-through). internal/build.ResolveImage(image, registryMirror) owns the precedence, highest first:
image — full ref override, used verbatim. Highest. Caveat: a local toolbox build tags the canonical ref, so with a full override imageplan.Ensure looks for the override ref and won't find the local build — image is a pull-source concern, not a build target.registry_mirror — swaps only the registry host of the canonical ref, preserving filippolmt/toolbox:latest (host split via build.SplitRegistryHost, shared with imagepull.registryOf). The relocated image is byte-identical, so a registry_mirror does satisfy Ensure. Caveat: a pull-through cache that hasn't ingested the image yet fails the first shell with manifest unknown — warm it (or pre-seed locally with pull: never), see troubleshooting.The pull policy (auto default | always | never) steers imageplan.Refresh: never skips the registry round-trip entirely (air-gapped — Ensure still hard-requires the image locally), always forces a pull bypassing the 1 h TTL cache (imagepull.ForcePull), auto is the cache-aware default (imagepull.RefreshIfStale). Env override requires the keys to be viper-seeded (SetDefault in config.Merge) — AutomaticEnv only resolves TOOLBOX_* for keys it already knows. Edit via toolbox config set --where global|local [--image|--registry-mirror|--pull] (empty value resets the key).
To layer your own tools onto the standard image without a repo change or a per-shell init.d/ script, drop a Dockerfile at ~/.toolbox/Dockerfile (retargeted with mounts_root / a profile root, same as every ~/.toolbox/-managed path). Its presence is the sole opt-in — global only, no per-repo or config-key activation. When present, toolbox shell builds a derived image tagged ghcr.io/filippolmt/toolbox:local on top of the resolved base and runs from it; when absent, the shell runs from the base image unchanged.
Append-only, RUN-only contract. The overlay is a bare fragment — it MUST NOT contain a FROM line and builds with an empty context (no COPY/ADD from host files, so nothing under ~/.toolbox/ is ever tarred into the build). Toolbox injects FROM <resolved base image ID> ahead of your fragment, so entrypoint, init.d/, and host-UID mapping are inherited unchanged. Use it for RUN sudo apt-get install … / RUN pip install …-style additions:
# ~/.toolbox/Dockerfile — no FROM line; RUN only.
RUN sudo apt-get update && sudo apt-get install -y --no-install-recommends \
httpie \
&& sudo rm -rf /var/lib/apt/lists/*
Rebuild triggers. A marker (base image ID + sha256 of the Dockerfile bytes) is stored under the toolbox state dir (~/.toolbox/toolbox/state/local-overlay.marker, mounts_root-aware — alongside the image-pull cache, so toolbox-managed state stays out of your config dir). The build is skipped when the marker matches and :local is present locally; otherwise it rebuilds. So a rebuild happens when you edit the Dockerfile, when imageplan.Refresh updates the base (its image ID changes), or when the :local image is missing. Base freshness stays governed by pull; :local carries pull policy never, so Refresh/Ensure never reach a registry for it. The first build streams its output and is unavoidably slower; later shells skip via the marker.
Fail-loud. A failing overlay build (e.g. a broken RUN) aborts the shell and surfaces the build log — Toolbox never silently falls back to the base image.
Next fresh container. A rebuilt :local takes effect for the next freshly-created container under the existing AutoRemove lifecycle. A running/stopped container is reused as-is and adopts the new image only when a fresh container is next created — toolbox stop (or exiting the shell) forces that. Rollback: delete ~/.toolbox/Dockerfile (revert to base) or docker rmi ghcr.io/filippolmt/toolbox:local.
browser_bridge (deprecated)browser_bridge: is the pre-rename spelling of bridge:. When bridge: is absent, the loader folds browser_bridge into it (fillDefaultsBackstop in internal/config/config.go); when both are set, bridge: wins. Migrate to bridge: — the alias survives only for config files written before the rename.
env: passthroughArbitrary env vars injected into the in-container shell via an env: map in .toolbox.yaml. Motivating case: opt-in env-gated CLI features like CLAUDE_CODE_WORKFLOWS=1 (Workflow tool), which previously needed a persisted ~/.zshrc edit.
env: # top-level — applies to every toolbox shell
CLAUDE_CODE_WORKFLOWS: "1"
CLAUDE_CODE_EFFORT_LEVEL: medium
PROMPT_HIDE_KUBE: "1" # hide the starship kubernetes segment
shells:
infra:
path: /tmp/infra
env: # per-shell — overlays the top-level map
AWS_PROFILE: prod
The kubernetes and gcloud prompt segments are opt-out via PROMPT_HIDE_KUBE / PROMPT_HIDE_GCLOUD (set to any value to hide). Details and why terraform/docker_context aren't env-toggleable: prompt module toggles.
GIT_CREDENTIAL_BRIDGE=0 disables the bridge git credential helper without uninstalling the bridge (default on when the bridge is installed). It is un-prefixed by necessity — the TOOLBOX_ prefix is reserved (see below), so this is the supported way to gate it.
Contract:
--config → walked-up .toolbox.yaml → ~/.toolbox.yaml → TOOLBOX_* env → defaults), so the top-level env: is set globally (~/.toolbox.yaml) or per-project (.toolbox.yaml). The per-shell shells.<name>.env overlays it for that named shell only — per-shell keys win on collision (config.Config.EffectiveEnv, keyed by the raw shells: name, not the sanitized container suffix).sessionplan emits the curated TOOLBOX_*/PWD/SDD entries first, then the loopback-bridge markers, then the user env sorted by key (sessionplan.userEnv, deterministic for tests).config.ValidateEnv rejects empty keys, keys containing =, and any key with the TOOLBOX_ prefix or the literal PWD — those are owned by the curated contract. Same rules apply per-entry under shells.<name>.env (errors namespaced as shells.<name>.env: …). Empty values are allowed (export VAR=). Keys are injected verbatim: environment-variable names are case-sensitive, so FOO and foo are distinct vars (both the top-level and per-shell maps preserve the case you write).tools: block, like sdd: / bridge: — flipping a key never invalidates the image hash. Takes effect on the next container create (toolbox stop first to refresh an existing one).Tunes toolbox worktree sessions. A worktree is a checkout of git-tracked files only, so create/open seed a curated set of gitignored per-repo working state from the main repo into the new worktree (.claude/settings.local.json, .env/.env.*, openspec/, gsd's .planning/). worktree.seed adds extra repo-relative paths to that set:
worktree:
seed:
- .secrets.local
- config/local.yaml
Contract:
seed entries alike — is copied only if git check-ignore reports it ignored in the main repo. A tracked path already arrives with the checkout; a non-ignored untracked path is left alone. So a seed entry that isn't gitignored is a silent no-op, and the built-in defaults self-correct in a repo that tracks one of them.seed is unioned with the built-in defaults (not a replacement); directories are copied recursively. Copies never clobber a worktree-local edit (an existing destination is kept).config.ValidateWorktreeSeed rejects absolute paths, entries containing .., and empty strings — the paths drive filesystem reads under the repo root and writes under the worktree, so traversal must not escape either tree.TOOLBOX_* environment variablesViper is configured with SetEnvPrefix("TOOLBOX") + AutomaticEnv, with the image-selection keys explicitly seeded so the override always resolves:
| Variable | Overrides |
|---|---|
TOOLBOX_IMAGE |
image |
TOOLBOX_REGISTRY_MIRROR |
registry_mirror |
TOOLBOX_PULL |
pull |
TOOLBOX_BRIDGE |
bridge |
For these four keys the TOOLBOX_* env var sits at the top of the loading order: it overrides every file layer (--config, project, global), and only the built-in default is below it.