Skip to content

fix: use the link farm when running stubs unactivated - #634

Merged
alecthomas merged 1 commit into
masterfrom
aat/unactivated-exec-perf
Oct 21, 2026
Merged

alecthomas merged 1 commit into
masterfrom
aat/unactivated-exec-perf

Conversation

@alecthomas

@alecthomas alecthomas commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Running ./bin/<tool> without activating the environment, as git hooks, IDEs and agents often do, is far slower than the same command in an activated shell. In a large repository whose just fmt recipe calls many Hermit-managed tools, ./bin/just fmt took about 22s unactivated against 6.6s activated (18.5s against 4.4s on a quieter machine).

The tool's children got bin/ on PATH but not the link farm, so every tool they ran went back through a stub. Those stubs also took the slow unactivated path, because ACTIVE_HERMIT is unset. On that path the stub resolves every installed package's manifest again. Per call, measured with bin/bash -c true in that repository:

Path Time
Unactivated stub 0.28s
Activated stub 0.05s
Link farm 0.009s

One unactivated ./bin/just fmt started 59 Hermit processes. 45 were bash, which that environment manages with Hermit and just uses to run recipe lines. Most of the extra time is the repeated environment reload, not process startup: activated shells before 0.54.0, which had no link farm, were already close to the current activated time.

Unactivated hermit exec now does what activation does: it checks linked channel packages for updates, creates the link farm, and puts it on PATH directly ahead of bin/. Activation and exec share this through a new createLinkFarm helper. Children then run other packages directly, as in an activated shell. The child environment differs from before only by the farm on PATH and HERMIT_LINK_FARM, and the farm is the same directory activation uses. Activated execution is unchanged.

Results for ./bin/just fmt in that repository, three runs each, on a loaded machine:

Build Not activated Activated
Before 21.9s, 21.2s, 22.7s 6.6s, 6.5s, 6.6s
After 6.2s, 6.0s, 6.1s 6.6s, 7.5s, 6.8s

Hermit processes per unactivated run went from 59 to 2.

Known gap: a child that calls a stub by an explicit path, such as xargs bin/go, still pays the full unactivated reload (about 0.28s against 0.05s activated). Fixing that needs a marker telling nested stubs the environment is already loaded, without making child shells think Hermit is activated, so it is left out of this change.

Tests: two new integration tests, run under bash and zsh, cover a child of an unactivated stub running another package through the farm with its environment variables set, and a due channel update being picked up when the child runs a linked channel package. The first fails on the previous code; the second fails if the update check is removed from exec.

🤖 Generated with Claude Code

Running `./bin/<tool>` without activation gave the tool's children
`bin/` on PATH but not the link farm, so every tool they ran went back
through a stub. Those stubs also took the slow unactivated path,
resolving every installed package's manifest again (~0.27s each,
against ~0.05s for an activated stub). In blox, `./bin/just fmt`
started 59 Hermit processes this way and took ~22s, against ~6.6s
when activated.

Unactivated `hermit exec` now does what activation does: it checks
linked channel packages for updates, creates the link farm and puts it
on PATH directly ahead of `bin/`. The same command now starts 2 Hermit
processes and takes ~6.1s. Activated execution is unchanged, and the
child environment differs only by the farm on PATH and
HERMIT_LINK_FARM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@alecthomas
alecthomas marked this pull request as ready for review October 21, 2026 03:25
@alecthomas
alecthomas merged commit 605be3b into master Oct 21, 2026
8 checks passed
@alecthomas
alecthomas deleted the aat/unactivated-exec-perf branch October 21, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants