Skip to content

Added Claude Code hook to auto-install deps in fresh worktrees#29049

Open
9larsons wants to merge 2 commits into
mainfrom
slars/beautiful-montalcini-ab9208
Open

Added Claude Code hook to auto-install deps in fresh worktrees#29049
9larsons wants to merge 2 commits into
mainfrom
slars/beautiful-montalcini-ab9208

Conversation

@9larsons

@9larsons 9larsons commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

no ref

Problem

A fresh git worktree checkout of the monorepo has no node_modules and no theme submodules, so pnpm dev (and most other commands) fail until pnpm run setup has been run manually. Anyone using Claude Code worktree sessions hits this on every new worktree.

Solution

A SessionStart hook in the shared .claude/settings.json that no-ops unless the session cwd is a ghost-monorepo checkout missing node_modules, and otherwise runs .claude/hooks/worktree-setup.sh, which bootstraps the checkout as fast as possible:

  • pnpm install and theme-submodule init run in parallel — they're independent, so wall time is bounded by the install alone
  • linked worktrees get their own submodule gitdirs, so a plain git submodule update --init re-clones the themes from GitHub on every new worktree; the script instead clones with --reference/--dissociate from a sibling checkout that already has the pinned commit (local-disk fast, works offline), with a plain network clone as fallback
  • bootstraps nvm, with a corepack fallback for non-nvm users
  • logs to ~/.claude/ghost-worktree-setup.log and injects a one-line success/failure status into the session context so the agent knows the state before doing any work

Measured on a warm pnpm store: serial pnpm run setup ≈ 25s (18s install + 6s network submodule clones); this script ≈ 17.5s.

Notes

  • Claude Code project hooks are approve-on-first-use, so each developer is prompted before this ever runs — it's opt-in per person.
  • The script is also runnable by hand from any checkout root: bash .claude/hooks/worktree-setup.sh
  • Tested end-to-end: standalone script run in a fresh worktree (17.5s, correct submodule SHAs, both rc=0) and a headless Claude session in a fresh worktree from this branch (hook fired through the launcher; deps + submodules present before the first turn).

no ref

- fresh worktree checkouts have no node_modules or theme submodules, so
  `pnpm dev` fails until `pnpm run setup` has been run manually
- a SessionStart hook now runs `pnpm run setup` automatically whenever a
  session starts in a ghost-monorepo checkout without node_modules, and
  no-ops everywhere else, so new Claude Code worktree sessions are ready
  to use immediately
- Claude Code project hooks are approve-on-first-use, so this is opt-in
  per developer
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch slars/beautiful-montalcini-ab9208

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nx-cloud

nx-cloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 21f868b

Command Status Duration Result
nx run @tryghost/admin:build ✅ Succeeded 10s View ↗
nx run ghost:build:assets ✅ Succeeded 1s View ↗
nx run-many -t lint -p ghost-monorepo ✅ Succeeded <1s View ↗
nx run ghost:build:tsc ✅ Succeeded 5s View ↗
nx run-many --target=build --projects=tag:publi... ✅ Succeeded 1s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-07-02 15:51:16 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/settings.json:
- Line 20: The setup guard in the `.claude/settings.json` command is using only
`node_modules` to decide whether to skip `pnpm run setup`, which can miss failed
submodule setup. Update the shell logic around the existing `cd`, `package.json`
check, and `pnpm run setup` flow to also verify submodule state before exiting
early, so a partially initialized checkout still reruns setup instead of
no-oping. Use the current command block as the place to add the additional
sentinel check alongside the existing `node_modules` condition.
- Line 20: The setup hook in the settings command continues even when `nvm use`
fails because the commands are chained with separators that don’t stop
execution. Update the shell flow in the hook so failure to switch Node versions
aborts before `pnpm run setup`, and keep the existing early-exit checks around
the `jq`, `package.json`, and `pnpm run setup` steps intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a301578a-36ec-4cb1-ac03-93a1f9a763c5

📥 Commits

Reviewing files that changed from the base of the PR and between ae505e8 and 46a8db4.

📒 Files selected for processing (1)
  • .claude/settings.json

Comment thread .claude/settings.json Outdated
"hooks": [
{
"type": "command",
"command": "jq -r '.cwd // empty' | { read -r d; cd \"$d\" 2>/dev/null || exit 0; [ \"$(jq -r '.name // empty' package.json 2>/dev/null)\" = \"ghost-monorepo\" ] || exit 0; [ -d node_modules ] && exit 0; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\" >/dev/null 2>&1; nvm use >/dev/null 2>&1; command -v pnpm >/dev/null 2>&1 || corepack enable pnpm >/dev/null 2>&1; if pnpm run setup >\"$HOME/.claude/ghost-worktree-setup.log\" 2>&1; then echo \"Fresh Ghost checkout: pnpm run setup completed (deps + submodules installed).\"; else echo \"Fresh Ghost checkout: pnpm run setup FAILED - check ~/.claude/ghost-worktree-setup.log and run it manually before pnpm dev.\"; fi; }",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t use node_modules as the only setup sentinel.

pnpm run setup can create node_modules before later setup steps fail, leaving missing submodules but causing future sessions to no-op silently. Check submodule state before skipping.

🐛 Proposed fix
-            "command": "jq -r '.cwd // empty' | { read -r d; cd \"$d\" 2>/dev/null || exit 0; [ \"$(jq -r '.name // empty' package.json 2>/dev/null)\" = \"ghost-monorepo\" ] || exit 0; [ -d node_modules ] && exit 0; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\" >/dev/null 2>&1; nvm use >/dev/null 2>&1; command -v pnpm >/dev/null 2>&1 || corepack enable pnpm >/dev/null 2>&1; if pnpm run setup >\"$HOME/.claude/ghost-worktree-setup.log\" 2>&1; then echo \"Fresh Ghost checkout: pnpm run setup completed (deps + submodules installed).\"; else echo \"Fresh Ghost checkout: pnpm run setup FAILED - check ~/.claude/ghost-worktree-setup.log and run it manually before pnpm dev.\"; fi; }",
+            "command": "jq -r '.cwd // empty' | { read -r d; cd \"$d\" 2>/dev/null || exit 0; [ \"$(jq -r '.name // empty' package.json 2>/dev/null)\" = \"ghost-monorepo\" ] || exit 0; if [ -d node_modules ] && ! git submodule status --recursive 2>/dev/null | grep -q '^[+-]'; then exit 0; fi; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\" >/dev/null 2>&1; nvm use >/dev/null 2>&1; command -v pnpm >/dev/null 2>&1 || corepack enable pnpm >/dev/null 2>&1; if pnpm run setup >\"$HOME/.claude/ghost-worktree-setup.log\" 2>&1; then echo \"Fresh Ghost checkout: pnpm run setup completed (deps + submodules installed).\"; else echo \"Fresh Ghost checkout: pnpm run setup FAILED - check ~/.claude/ghost-worktree-setup.log and run it manually before pnpm dev.\"; fi; }",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"command": "jq -r '.cwd // empty' | { read -r d; cd \"$d\" 2>/dev/null || exit 0; [ \"$(jq -r '.name // empty' package.json 2>/dev/null)\" = \"ghost-monorepo\" ] || exit 0; [ -d node_modules ] && exit 0; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\" >/dev/null 2>&1; nvm use >/dev/null 2>&1; command -v pnpm >/dev/null 2>&1 || corepack enable pnpm >/dev/null 2>&1; if pnpm run setup >\"$HOME/.claude/ghost-worktree-setup.log\" 2>&1; then echo \"Fresh Ghost checkout: pnpm run setup completed (deps + submodules installed).\"; else echo \"Fresh Ghost checkout: pnpm run setup FAILED - check ~/.claude/ghost-worktree-setup.log and run it manually before pnpm dev.\"; fi; }",
"command": "jq -r '.cwd // empty' | { read -r d; cd \"$d\" 2>/dev/null || exit 0; [ \"$(jq -r '.name // empty' package.json 2>/dev/null)\" = \"ghost-monorepo\" ] || exit 0; if [ -d node_modules ] && ! git submodule status --recursive 2>/dev/null | grep -q '^[+-]'; then exit 0; fi; export NVM_DIR=\"$HOME/.nvm\"; [ -s \"$NVM_DIR/nvm.sh\" ] && . \"$NVM_DIR/nvm.sh\" >/dev/null 2>&1; nvm use >/dev/null 2>&1; command -v pnpm >/dev/null 2>&1 || corepack enable pnpm >/dev/null 2>&1; if pnpm run setup >\"$HOME/.claude/ghost-worktree-setup.log\" 2>&1; then echo \"Fresh Ghost checkout: pnpm run setup completed (deps + submodules installed).\"; else echo \"Fresh Ghost checkout: pnpm run setup FAILED - check ~/.claude/ghost-worktree-setup.log and run it manually before pnpm dev.\"; fi; }",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/settings.json at line 20, The setup guard in the
`.claude/settings.json` command is using only `node_modules` to decide whether
to skip `pnpm run setup`, which can miss failed submodule setup. Update the
shell logic around the existing `cd`, `package.json` check, and `pnpm run setup`
flow to also verify submodule state before exiting early, so a partially
initialized checkout still reruns setup instead of no-oping. Use the current
command block as the place to add the additional sentinel check alongside the
existing `node_modules` condition.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'File lines around the reported location:\n'
nl -ba .claude/settings.json | sed -n '1,80p'

printf '\nSearch for related setup commands:\n'
rg -n '"nvm use|pnpm run setup|ghost-worktree-setup|node_modules' .claude/settings.json

Repository: TryGhost/Ghost

Length of output: 233


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import json

p = Path('.claude/settings.json')
text = p.read_text()
data = json.loads(text)

def walk(obj, path=''):
    if isinstance(obj, dict):
        for k, v in obj.items():
            np = f'{path}.{k}' if path else k
            if k == 'command':
                print(f'COMMAND at {np}:')
                print(v)
                print()
            walk(v, np)
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            walk(v, f'{path}[{i}]')

walk(data)
PY

printf '\nShell behavior probe:\n'
bash -lc 'false >/dev/null 2>&1; echo after-false; true' 2>&1 | sed -n '1,5p'

printf '\nCommand-structure probe:\n'
python3 - <<'PY'
cmd = r'''export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" >/dev/null 2>&1; nvm use >/dev/null 2>&1; command -v pnpm >/dev/null 2>&1 || corepack enable pnpm >/dev/null 2>&1; if pnpm run setup >"$HOME/.claude/ghost-worktree-setup.log" 2>&1; then echo "Fresh Ghost checkout: pnpm run setup completed (deps + submodules installed)."; else echo "Fresh Ghost checkout: pnpm run setup FAILED - check ~/.claude/ghost-worktree-setup.log and run it manually before pnpm dev."; fi;'''
print(cmd)
PY

Repository: TryGhost/Ghost

Length of output: 1774


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import json

p = Path('.claude/settings.json')
data = json.loads(p.read_text())

def find_commands(obj, path=''):
    if isinstance(obj, dict):
        for k, v in obj.items():
            np = f'{path}.{k}' if path else k
            if k == 'command':
                print(f'PATH: {np}')
                print(v)
                print('---')
            find_commands(v, np)
    elif isinstance(obj, list):
        for i, v in enumerate(obj):
            find_commands(v, f'{path}[{i}]')

find_commands(data)
PY

python3 - <<'PY'
# Small semantics probe for a similar construct: failing command before a semicolon
# does not stop subsequent commands unless the shell is told to exit.
import subprocess, textwrap
script = 'false >/dev/null 2>&1; echo after-false; true'
out = subprocess.run(['bash', '-lc', script], capture_output=True, text=True)
print(out.stdout.strip())
PY

Repository: TryGhost/Ghost

Length of output: 1130


Stop setup when nvm use fails. The current ; lets the hook keep going under the active Node version, so a missing .nvmrc runtime can send pnpm run setup through with the wrong environment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/settings.json at line 20, The setup hook in the settings command
continues even when `nvm use` fails because the commands are chained with
separators that don’t stop execution. Update the shell flow in the hook so
failure to switch Node versions aborts before `pnpm run setup`, and keep the
existing early-exit checks around the `jq`, `package.json`, and `pnpm run setup`
steps intact.

no ref

- `pnpm install` and theme submodule init are independent, so the setup
  hook now runs them in parallel: fresh-worktree bootstrap drops from
  ~25s to ~17s (bounded by pnpm install alone)
- linked worktrees re-clone theme submodules from GitHub every time
  because each worktree gets its own submodule gitdir; submodules are
  now cloned with --reference/--dissociate from a sibling checkout that
  already has the pinned commit (local-disk fast, works offline), with
  a network fallback
- moved the hook body out of the settings.json one-liner into
  .claude/hooks/worktree-setup.sh so it is reviewable, testable, and
  runnable by hand
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