Repository navigation
chore(hooks): install dependencies at session start and worktree entry - #118
Merged
Merged
Conversation
Fresh cloud clones and fresh git worktrees start without node_modules. A committed SessionStart hook and a PostToolUse hook on EnterWorktree run install-deps.sh, which loads nvm and runs npm ci only when the checkout has no node_modules, or when the hook's own last install used a different package-lock.json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
|
umm-actually re-reviewed at 1 new finding(s) posted (8 tracked finding(s) across all runs). umm-actually · deepseek/deepseek-v4.1-flash |
- Re-read the lock owner after the takeover claim is held, so a session never removes a lock another session just took over. - Run the interrupted-install recovery only while holding the lock, so a half-written tree from a still-running npm ci is never stamped as complete. - Re-read an empty lock pid after one second, so a lock whose pid write is still in flight is not treated as abandoned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lockfile - The marker, stamp, and lock move from .claude/ to the checkout's git directory, which is per-worktree and never tracked, so a checkout whose .gitignore predates the hook never shows them as untracked files. - The marker now holds the lockfile hash its install used, and interrupted-install recovery runs only when that hash matches the current lockfile, so a tree built from an older lockfile is reinstalled instead of stamped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aling mkdir locks The pid-file lock needed a takeover protocol to recover from a dead owner, and every takeover built from rm and mkdir left a check-then-act gap where two sessions could both install. A flock on a lock file in the git directory replaces it. The kernel releases the lock when every holder exits, so there is nothing to steal. npm ci inherits the locked descriptor, so an orphaned install keeps the lock until it finishes. Perl provides the flock call on both macOS and Linux; without perl the hook installs unlocked and says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Fresh checkouts start without
node_modules. A cloud (claude.ai/code) session clones the repo after the environment's setup script has already run, and a new git worktree never inherits the main checkout'snode_modules. Every agent session in one of those checkouts has to notice the missing dependencies and install them before tests, lint, or build can run.Change
.claude/settings.json(new, committed) registersinstall-deps.shonSessionStart(startup|resume) and onPostToolUseforEnterWorktree, with a 600s timeout..claude/hooks/install-deps.sh(new, committed):cwd, which follows the session into a worktree.$CLAUDE_PROJECT_DIRstays at the project root, so it is only the fallback.node_modulesexists, unless an install was interrupted or the hook's own last install used a differentpackage-lock.json. An install the hook did not make is never replaced.flockon a lock file.npm ciinherits the locked descriptor, so an orphaned install keeps the lock until it finishes, and the kernel releases it once every holder exits. No stale lock is ever left behind. A session that finds the lock held waits up to 480s, then re-checks whether the other session already installed. Perl provides theflockcall, because macOS lacksflock(1)and Linux lackslockf(1). When Perl is missing or the lock call fails for any reason other than the 480s timeout, the hook installs unlocked and logs why.git rev-parse --absolute-git-dir). That directory is per-worktree and never tracked, so no checkout shows them as untracked files.npm ciretry next session. The marker holds the lockfile hash its install used. Under the lock, a tree that passesnpm lswith a matching hash is accepted instead of reinstalled.$HOME/.nvm, or/root/.nvmfor cloud setup), switches to the.nvmrcNode when it is already installed, and runsnpm ciwith all output on stderr..gitignore:.claude/*with re-includes for exactly these two files.settings.local.json,worktrees/, and local hook files stay ignored, including in cloud clones without a global gitignore.AGENTS.md: the structure tree lists the new files.Verification
git check-ignore -v --no-index:settings.local.json,worktrees/, and an extra hook file are ignored.settings.jsonandinstall-deps.share re-included.Each case below ran against a real checkout:
node_modulesnpm ci(171 packages), stamp written, marker removednpm lspassesnode_modulesnpm ci. The other waits, then reports the install finishednpm ciinto the samenode_modules, confirming the lock is what serializes them.claude/,.gitignore, orpackage.jsonnpm cifails, the marker lands in.git/, exit 0,git statusemptygit status --untracked-files=allin the worktree shows no hook state after the runs.The
PostToolUseregistration onEnterWorktreefires in practice. Session transcripts from another repo using the same registration show it runningnpm ciin newly entered worktrees.shellcheckis clean.npm run lintandnpm test(800 tests) pass.🤖 Generated with Claude Code