Created
August 11, 2026 15:09
-
-
Save B4nan/29db31f4abe23cd6e486a3388bc54e2e to your computer and use it in GitHub Desktop.
Installing deps on SessionStart hook for worktrees
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
| #!/usr/bin/env bash | |
| # SessionStart hook — keep node deps in sync with the lockfile. Covers both the fresh | |
| # worktree (node_modules absent) and the REUSED worktree whose node_modules was installed | |
| # against an older lockfile (real case: apify-sdk-js Aug 2026, June install vs a same-day | |
| # crawlee bump → phantom type errors). NON-BLOCKING: the install is spawned fully detached | |
| # and the hook returns immediately, so session start is never held up. | |
| [ -f package.json ] || exit 0 # only node projects | |
| # Freshness check: a stamp with the lockfile hash is written after each successful install. | |
| # Stamp matches → node_modules is provably in sync → skip (zero cost). Anything else → | |
| # reinstall. The stamp (not `ni --frozen` unconditionally) is what makes "always install" | |
| # safe generically: for npm projects `ni --frozen` = `npm ci`, which nukes node_modules and | |
| # does a full reinstall every time. | |
| LOCKHASH="" | |
| for f in pnpm-lock.yaml yarn.lock package-lock.json npm-shrinkwrap.json bun.lock bun.lockb; do | |
| [ -f "$f" ] && LOCKHASH="$LOCKHASH$(shasum "$f")" | |
| done | |
| STAMP="node_modules/.claude-deps-lockhash" | |
| if [ -d node_modules ]; then | |
| # No lockfile → nothing to compare against; keep the legacy "present → skip" behavior. | |
| [ -z "$LOCKHASH" ] && exit 0 | |
| [ "$(cat "$STAMP" 2>/dev/null)" = "$LOCKHASH" ] && exit 0 | |
| fi | |
| # Hooks run in a non-interactive shell. If node wasn't inherited on PATH, fall back to | |
| # initializing the version manager so node/npm resolve. fnm is only a PATH fallback here. | |
| if ! command -v node >/dev/null 2>&1; then | |
| export PATH="/opt/homebrew/bin:$PATH" | |
| command -v fnm >/dev/null 2>&1 && eval "$(fnm env 2>/dev/null)" | |
| fi | |
| LOG="${TMPDIR:-/tmp}/claude-deps-$(printf '%s' "$PWD" | shasum | cut -c1-12).log" | |
| printf '[%s] %s\n' "$(date)" "$PWD" > "$LOG" | |
| # Detach fully: redirect every fd off the hook's pipe (and </dev/null) so the parent hook | |
| # isn't kept alive waiting on the child — that is what makes this non-blocking. PATH and the | |
| # stamp vars are exported so the child inherits them. | |
| # - Ensure `ni` is installed (it lives in the per-node-version global npm bin, so it can | |
| # disappear after a node version switch); install it via npm if absent. | |
| # - Then run `ni --frozen` (→ install with a frozen lockfile): a worktree only tests branch | |
| # source, never edits deps, so this is faster (skips lockfile resolution) and fails loudly | |
| # if package.json and the lockfile are out of sync instead of silently installing the wrong tree. | |
| # - On success, write the lockfile-hash stamp so future in-sync sessions skip instantly. | |
| export LOCKHASH STAMP | |
| nohup bash -c " | |
| cd \"$PWD\" || exit 1 | |
| command -v ni >/dev/null 2>&1 || npm i -g @antfu/ni | |
| ni --frozen | |
| rc=\$? | |
| [ \$rc -eq 0 ] && [ -n \"\$LOCKHASH\" ] && printf '%s' \"\$LOCKHASH\" > \"\$STAMP\" | |
| printf '\n__DEPS_DONE__ exit=%s\n' \"\$rc\" | |
| " >>"$LOG" 2>&1 </dev/null & | |
| if [ -d node_modules ]; then | |
| echo "📦 node_modules is out of sync with the lockfile — reinstalling in the background via 'ni --frozen'." | |
| else | |
| echo "📦 node_modules missing — installing deps in the background via 'ni --frozen' (ensuring ni via npm first if needed)." | |
| fi | |
| echo " Progress/log: $LOG" | |
| echo " IMPORTANT: before running any code or tests, wait until that log ends with '__DEPS_DONE__ exit=0'." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment