Skip to content

feat(herdr): install dependencies in a new worktree - #89

Open
fveracoechea wants to merge 2 commits into
mainfrom
feat/worktree-dependency-install
Open

fveracoechea wants to merge 2 commits into
mainfrom
feat/worktree-dependency-install

Conversation

@fveracoechea

Copy link
Copy Markdown
Owner

Summary

A Herdr worktree is a fresh checkout with no node_modules.
In a consumer such as mystique, each Claude hook runs bun run jarvis ..., so every tool call in a new worktree fails with Script not found "jarvis".
The operator.wake plugin now handles worktree.created and installs from the lockfile.

 herdr/
   herdr-plugin.toml      # version 0.2.0
+    [[events]] on = "worktree.created" -> bun worktree.ts
+  worktree.ts            # picks the install from the lockfile
+  worktree.test.ts       # skipped by the release build
on(worktree.created)
  path = event.data.worktree.path
  bun.lock | bun.lockb                     -> bun install --frozen-lockfile   (bun wins when both exist)
  package-lock.json | npm-shrinkwrap.json  -> npm ci
  no lockfile                              -> skip
  install exit != 0                        -> hook exits with that code (shown in plugin log)

Start reading at herdr/worktree.ts.
Things that look wrong and are not:

  • build.ts now skips *.test.ts when it copies herdr/ and skills/. The existing assert that the artifact has no .test. file guards it. It fails if the skip is removed.
  • The release test compares realpath. On macOS the temporary directory is a symlink, and that test failed locally on main before this change.

Evidence

  • Before: real Herdr, herdr worktree create --cwd ~/Code/mystique ..., then the Claude hook command in the new worktree:
    error: Script not found "jarvis", exit 1, no node_modules.
    After: the same steps with this branch's plugin linked. The plugin log shows Running \bun install --frozen-lockfile`andInstalled dependencies, exit 0. git status` is clean, and the hook command exits 0.
  • herdr/worktree.test.ts, 10 tests:
    a bun checkout whose dependency ships a bin fails bun run tool before the hook and passes after it.
    A stale lockfile fails the hook and stays unchanged.
    A table covers each lockfile name, plus both lockfiles together.
    A failed install, a missing npm, and no lockfile are also covered.
  • Each of these mutations fails at least one test: no --frozen-lockfile, dropping bun.lockb or npm-shrinkwrap.json, npm before bun, and a swallowed spawn error.
  • bun run quality passes, with 594 tests.

Verified Herdr behavior (0.9.x): the hook starts about 50 ms before herdr worktree create returns, and the CLI does not wait for it.
A 150 s hook ran to completion.
A warm bun install in mystique takes 1.4 s.

Not proven: the end-to-end run used a scratch copy of herdr/ with min_herdr_version lowered to 0.9.0, because the local Herdr server is 0.9.0. The scripts were the same bytes.

Merge Danger

Door: two-way

Revert the commit and relink the plugin.

Blast Radius: machine-wide

After a relink, every worktree that Herdr creates on the machine gets an install, in any project, with no opt-out.
The install runs in the background, so a slow npm ci can still lose the race with an agent's first tool call.
When the project runs Operator through bun run operator, the Operative brief keeps its own bun install --frozen-lockfile step as the fallback.
Review ran two frozen bun installs at once in one directory five times, and every run passed.
npm ci runs dependency lifecycle scripts, including in worktrees of untrusted branches. Bun blocks them by default.

Not in this PR: pnpm and yarn, a lockfile below the worktree root, worktree.opened, and an opt-out setting.

🤖 Generated with Claude Code

A Herdr worktree is a fresh checkout with no node_modules. In a project
whose Claude hooks run a package bin, such as `bun run jarvis`, every tool
call in that worktree reports `Script not found "jarvis"`.

The plugin now handles `worktree.created`. It picks the package manager
from the lockfile and runs a frozen install, `bun install
--frozen-lockfile` or `npm ci`, so the lockfile is never rewritten and the
checkout stays removable. Herdr starts the hook and returns from
`worktree create` without waiting, and a warm bun install finishes long
before an agent's first tool call.

The release build skips test files under herdr/, so the hook's test sits
beside it. The release test now compares real paths, because the macOS
temporary directory is a symlink and it failed locally.
Review found tests that could not fail: dropping `--frozen-lockfile`, a
lockfile name, or the bun-over-npm order kept them green. A table test now
covers each lockfile and the both-lockfiles case, and a stale lockfile must
fail the hook and stay unchanged.

The hook runs the install with the Bun that runs it, so PATH no longer
decides which Bun installs, and a missing npm reports a clear message
instead of a stack trace.

WAKE.md no longer claims the first tool call works. The install runs in the
background, so it says a hook can still fail until the install ends, and
that the Operative brief keeps its own install step as the fallback. It also
says that `npm ci` runs dependency lifecycle scripts, which bun blocks.
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.

1 participant