Silo v0.5.0 release notes

Pyenv-style shim fall-through. Tools added via `silo use` no longer shadow your homebrew binaries everywhere — only inside projects that claim them.

Silo v0.5.0 changes how ~/.silo/bin/<tool> shims behave for tools you added via silo use + silo sync. They now fall through to the next instance on PATH (homebrew, pyenv, system) when invoked outside a project that claims them. silo install-installed tools still claim their command everywhere, exactly as before.

The problem

Before v0.5.0, every shim in ~/.silo/bin/ was effectively a global claim. If you ran silo use node@22 in one project and let silo sync install it, ~/.silo/bin/npm would shadow your /opt/homebrew/bin/npm system-wide. That made silo feel invasive for users who only wanted per-project version pinning, not a global toolchain takeover.

The user-facing complaint was a one-liner: “I have homebrew npm. I added silo node to one project. Now which npm says silo for every project on this machine.”

The mechanism

v0.5.0 distinguishes two install paths via a new flag, pinnedGlobally, in ~/.silo/config.yaml:

When a ~/.silo/bin/<tool> shim is invoked, silo run walks .siloconf up from the cwd, then checks the global pin, then falls through:

[A] If the merged .siloconf claims this tool   →  silo dispatches into the VM
[B] Else if pinnedGlobally is true             →  silo dispatches into the VM
[C] Otherwise                                  →  strip ~/.silo/bin/ from PATH,
                                                  exec next instance (homebrew/pyenv/system)

This matches pyenv’s model: which python always shows pyenv’s shim, but the actual interpreter that runs depends on cwd.

Two-projects example

# ~/work/proj-a/.siloconf
tools: [node]
overrides:
  node: { image: docker.io/library/node:22-slim }
# ~/work/proj-b/.siloconf
tools: [node]
overrides:
  node: { image: docker.io/library/node:18-slim }
cd ~/work/proj-a && node --version  # → v22.x  (silo, branch [A])
cd ~/work/proj-b && node --version  # → v18.x  (silo, branch [A])
cd ~                && node --version  # → homebrew node  (branch [C])
which node                          # → ~/.silo/bin/node  (always)

New commands

silo list gained a PINNED column. silo current now shows [project] / [pinned] / [fall-through] per tool so it’s obvious which dispatch branch will fire from your current cwd.

Migration

Existing installs predate the flag. On first load of ~/.silo/config.yaml after upgrading, every entry defaults to pinnedGlobally: true. Behavior is unchanged for tools you’ve already installed — you keep the global claim until you explicitly run silo unpin.

The schema bumped from version: 1 to version: 2. The migration is in-place and idempotent; no manual action needed.

When you’d use each path

Why not project-local PATH

The earlier idea was per-project <root>/.silo/bin/ directories with a chpwd shell hook that swapped them on/off PATH. That works but adds shell magic, breaks in non-interactive shells, and depends on shell-specific hook plumbing.

Pyenv-style fall-through has zero shell magic. One PATH entry, one dispatch decision in silo run, works in any environment that puts ~/.silo/bin/ on PATH — including IDE terminals, CI runners, and bash -c subshells.

Compatibility

Upgrade with brew upgrade silo (Homebrew tap) or rebuild from source. silo --version should report 0.5.0.

← all posts