hooks

Install Claude Code hooks and git hooks for automatic formatting, linting, and context monitoring. Use when setting up a project, after "install hooks", "set up hooks", "add auto-formatting", "add git hooks", "set up husky", or when starting a new project that uses Biome.

16 stars

Best use case

hooks is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Install Claude Code hooks and git hooks for automatic formatting, linting, and context monitoring. Use when setting up a project, after "install hooks", "set up hooks", "add auto-formatting", "add git hooks", "set up husky", or when starting a new project that uses Biome.

Teams using hooks should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/hooks/SKILL.md --create-dirs "https://raw.githubusercontent.com/howells/arc/main/skills/hooks/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/hooks/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How hooks Compares

Feature / AgenthooksStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Install Claude Code hooks and git hooks for automatic formatting, linting, and context monitoring. Use when setting up a project, after "install hooks", "set up hooks", "add auto-formatting", "add git hooks", "set up husky", or when starting a new project that uses Biome.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

<tool_restrictions>
# MANDATORY Tool Restrictions

## REQUIRED TOOLS — use these, do not skip:
- **`AskUserQuestion`** — Preserve the one-question-at-a-time interaction pattern for user choices. In Claude Code, use the tool. In Codex, ask one concise plain-text question at a time unless a structured question tool is actually available in the current mode. Do not narrate missing tools or fallbacks to the user.

## BANNED TOOLS — calling these is a skill violation:
- **`EnterPlanMode`** — BANNED. Do NOT call this tool. This skill has its own structured process. Execute the steps below directly.
- **`ExitPlanMode`** — BANNED. You are never in plan mode.
</tool_restrictions>

<process>

## Step 0: Handle flags

**`--remove`:** Remove all Arc-installed hooks (Claude Code + git hooks). Read `.claude/settings.json`, remove Arc hooks, write back. Remove Arc-created git hooks from `.husky/` or `.vite-hooks/`. Report what was removed. Done — skip all other steps.

**`--git-only`:** Skip Claude Code hooks (Steps 1-7). Jump directly to Step 8 (git hooks).

**`--claude-only`:** Install only Claude Code hooks (Steps 1-7). Skip Step 8 (git hooks).

**No flag (default):** Install both Claude Code hooks AND git hooks.

## Step 1: Detect Biome

```bash
grep -q '"@biomejs/biome"' package.json 2>/dev/null
```

**If Biome is in package.json:** Continue to Step 2.

**If Biome is NOT found:**

```yaml
AskUserQuestion:
  question: "Biome not found in package.json. Arc hooks require Biome for auto-formatting and linting. How would you like to proceed?"
  header: "Biome Required"
  options:
    - label: "Install Biome and continue"
      description: "Add @biomejs/biome as a dev dependency and set up all hooks"
    - label: "Skip formatting hooks"
      description: "Install context monitor and git guard only, skip Biome-dependent hooks"
    - label: "Cancel"
      description: "Exit without installing any hooks"
```

**If user picks "Install Biome and continue":**
```bash
# Detect package manager from lockfile
if [ -f pnpm-lock.yaml ]; then
  pnpm add -D @biomejs/biome
elif [ -f yarn.lock ]; then
  yarn add -D @biomejs/biome
else
  npm install -D @biomejs/biome
fi
```

Then check for `biome.json` / `biome.jsonc`. If missing:
```bash
npx @biomejs/biome init
```

**If user picks "Skip formatting hooks":** Set `SKIP_BIOME=true`, continue to Step 3.

**If user picks "Cancel":** Stop. Do not install any hooks.

## Step 2: Verify Biome works

```bash
./node_modules/.bin/biome --version
```

If this fails, the binary isn't available. Tell the user and offer to reinstall.

Also check for biome config:
```bash
ls biome.json biome.jsonc 2>/dev/null
```

If no config exists, note this — biome will use defaults, which is fine.

## Step 3: Build the hooks config

Build the hooks object to merge into `.claude/settings.json`.

**Biome format hook (PostToolUse, Edit|Write):**
```json
{
  "matcher": "Edit|Write|NotebookEdit",
  "hooks": [
    {
      "type": "command",
      "command": "jq -r '.tool_input.file_path // .tool_input.filePath // empty' | { read file_path; case \"$file_path\" in *.js|*.ts|*.jsx|*.tsx|*.json|*.jsonc|*.css|*.graphql) ./node_modules/.bin/biome format --write \"$file_path\" 2>/dev/null || true ;; esac; }"
    }
  ]
}
```

**Biome lint hook (Stop):**
```json
{
  "hooks": [
    {
      "type": "command",
      "command": "git diff --name-only --diff-filter=d HEAD 2>/dev/null | grep -E '\\.(js|ts|jsx|tsx|json|jsonc|css|graphql)$' | xargs -r ./node_modules/.bin/biome check --fix --unsafe 2>/dev/null || true"
    }
  ]
}
```

**TypeScript check hook (Stop):**

Only include this if the project has TypeScript (`tsconfig.json` exists).

```json
{
  "hooks": [
    {
      "type": "command",
      "command": "npx tsc --noEmit 2>&1 | tail -20 || true"
    }
  ]
}
```

**Git guard hook (PreToolUse, Bash):**

This hook blocks destructive git operations before they execute. Always install — not dependent on Biome.

```json
{
  "matcher": "Bash",
  "hooks": [
    {
      "type": "command",
      "command": "jq -r '.tool_input.command' | grep -qE 'git\\s+(reset\\s+--hard|push\\s+(-f|--force)|clean\\s+-f|checkout\\s+\\.)' && echo '{\"decision\":\"block\",\"reason\":\"Destructive git operation blocked by Arc hooks. Ask the user first.\"}' || true"
    }
  ]
}
```

**Context monitor hook (PostToolUse, all tools):**

Determine the absolute path to Arc's `hooks/` directory by resolving the Arc install root from this skill's location. Use that resolved path as `${ARC_HOOKS_PATH}`.

Read `hooks/arc-context-monitor.js` from the resolved Arc install root to confirm it exists.

```json
{
  "matcher": "",
  "hooks": [
    {
      "type": "command",
      "command": "node ${ARC_HOOKS_PATH}/arc-context-monitor.js"
    }
  ]
}
```

Where `${ARC_HOOKS_PATH}` is the resolved absolute path to the Arc plugin's `hooks/` directory.

**Statusline hook:**
```json
{
  "type": "command",
  "command": "node ${ARC_HOOKS_PATH}/arc-statusline.js"
}
```

**If SKIP_BIOME is true:** Only include the git guard, context monitor, and statusline hooks. The tsc hook is independent of Biome — include it if `tsconfig.json` exists.

## Step 4: Read existing settings

```bash
cat .claude/settings.json 2>/dev/null
```

**If file doesn't exist:**
```bash
mkdir -p .claude
```
Start with an empty object `{}`.

**If file exists:** Parse the JSON. Preserve ALL existing fields (permissions, enabledMcpjsonServers, enableAllProjectMcpServers, enabledPlugins, etc.).

## Step 5: Merge hooks into settings

This is the critical step. NEVER clobber existing hooks — merge with them.

**Read the existing `hooks` object** (may be undefined).

**For PreToolUse:**
- Get existing `hooks.PreToolUse` array (or empty array)
- Check if an Arc git guard hook already exists (command contains `git.*reset.*--hard`)
- Add only if it doesn't exist

**For PostToolUse:**
- Get existing `hooks.PostToolUse` array (or empty array)
- Check if an Arc biome format hook already exists (command contains `biome format`)
- Check if an Arc context monitor hook already exists (command contains `arc-context-monitor`)
- Add new entries only if they don't already exist
- Biome format hook and context monitor hook are separate entries (different matchers)

**For Stop:**
- Get existing `hooks.Stop` array (or empty array)
- Check if an Arc biome lint hook already exists (command contains `biome check`)
- Check if an Arc tsc hook already exists (command contains `tsc --noEmit`)
- Add each only if it doesn't exist
- If no `tsconfig.json` in the project, skip the tsc hook

**For Statusline:**
- Get existing `hooks.Statusline` array (or empty array)
- Check if Arc statusline already exists (command contains `arc-statusline`)
- Add only if it doesn't exist

**Write the merged settings back:**
Use the Write tool to write the complete JSON (pretty-printed with 2-space indent).

**CRITICAL: Preserve all non-hook fields exactly as they were.** Do not add, remove, or modify anything outside the `hooks` key.

## Step 6: Verify installation

Read back `.claude/settings.json` and confirm the hooks are present.

Count installed hooks:
- PreToolUse entries
- PostToolUse entries
- Stop entries
- Statusline entries

## Step 7: Report

```
Arc hooks installed in .claude/settings.json

  PreToolUse  (Bash)        →  blocks destructive git ops (force push, reset --hard)
  PostToolUse (Edit|Write)  →  biome format --write on edited file
  PostToolUse (all tools)   →  context monitor (warns at 35%/25% remaining)
  Stop                      →  biome check --fix --unsafe on all changed files
  Stop                      →  tsc --noEmit (if tsconfig.json exists)
  Statusline                →  context bar showing usage

Hooks run automatically — zero token cost, zero agent effort.

To remove: /arc:hooks --remove
```

If `SKIP_BIOME` was true:
```
Arc hooks installed in .claude/settings.json

  PreToolUse  (Bash)        →  blocks destructive git ops (force push, reset --hard)
  PostToolUse (all tools)   →  context monitor (warns at 35%/25% remaining)
  Statusline                →  context bar showing usage

Biome hooks skipped (not installed). Run /arc:hooks again after adding Biome.

To remove: /arc:hooks --remove
```

## Step 8: Install git hooks (Husky or Vite+)

This step installs pre-commit and pre-push git hooks to enforce typecheck + lint at the git level. This catches errors that Claude Code hooks can't — like commits made outside Claude, or when the AI session ends before running Stop hooks.

### Step 8a: Detect hook system

Check which hook system the project uses:

1. **Vite+ project:** `.vite-hooks/` directory exists, OR `package.json` has `"prepare": "vp config"` → use Vite+ hooks
2. **Husky project:** `.husky/` directory exists, OR `package.json` has `"prepare": "husky"` → use Husky hooks
3. **Neither:** Install Husky (see Step 8b)

### Step 8b: Ensure hook infrastructure exists

**For Husky projects (or new installs):**

Check if husky is installed:
```bash
grep -q '"husky"' package.json 2>/dev/null
```

If not installed:
```bash
# Detect package manager
if [ -f pnpm-lock.yaml ]; then
  pnpm add -D husky lint-staged
elif [ -f bun.lockb ] || [ -f bun.lock ]; then
  bun add -D husky lint-staged
elif [ -f yarn.lock ]; then
  yarn add -D husky lint-staged
else
  npm install -D husky lint-staged
fi
```

Ensure `prepare` script exists in package.json:
```json
"scripts": { "prepare": "husky" }
```

Ensure `.husky/` directory exists:
```bash
mkdir -p .husky
```

**For Vite+ projects:** Hooks go in `.vite-hooks/`. Ensure directory exists:
```bash
mkdir -p .vite-hooks
```

### Step 8c: Configure lint-staged

Check if lint-staged config exists in `package.json`. If not, add it:

```json
"lint-staged": {
  "*.{js,ts,jsx,tsx,json,jsonc,css}": "biome format --write --no-errors-on-unmatched"
}
```

**For Vite+ projects:** lint-staged is handled by `vp staged` configured through `vite.config.ts`. Do not add lint-staged config to package.json. If `vp staged` is not configured, check vite.config.ts for staged config. If missing, note this to the user.

### Step 8d: Create hook files

Determine the hooks directory (`$HOOKS_DIR`):
- Husky: `.husky/`
- Vite+: `.vite-hooks/`

**pre-commit** (`$HOOKS_DIR/pre-commit`):

For Husky projects:
```sh
pnpm lint-staged
pnpm typecheck && pnpm lint
```

For Vite+ projects:
```sh
vp staged
```

For bun-based projects (detected by `bun.lockb` or `bun.lock`):
```sh
bun run typecheck && bunx lint-staged
```

**pre-push** (`$HOOKS_DIR/pre-push`):

For Husky projects:
```sh
pnpm typecheck || exit 1
pnpm lint || exit 1
```

For Vite+ projects (adapt to the project's existing scripts):
```sh
pnpm typecheck || exit 1
pnpm lint || exit 1
```

Make both files executable:
```bash
chmod +x $HOOKS_DIR/pre-commit $HOOKS_DIR/pre-push
```

**Important:** If hook files already exist, read them first. Only overwrite if they are missing typecheck or lint steps. Never remove project-specific steps that are already present (like env validation, SDK guards, test runs, etc.).

### Step 8e: Fix turbo.json cache (Turborepo projects only)

If `turbo.json` exists, ensure `typecheck` and `lint` tasks have `cache: false`:

```bash
# Read turbo.json and check cache settings
```

For each task (`typecheck`, `lint`, `check-types`):
- If the task exists and `cache` is not `false`, set `cache: false`
- If the task does not exist, skip it (don't add tasks that aren't defined)

**This is non-negotiable.** Turborepo caching on typecheck/lint is the #1 cause of type errors slipping through git hooks. Turbo replays a cached success exit code without actually running tsc or biome, making hooks pass when they should fail.

**Do NOT disable cache on `build` or `test` tasks** — those have legitimate outputs and benefit from caching.

### Step 8f: Report git hooks

```
Git hooks installed in $HOOKS_DIR/

  pre-commit  →  lint-staged (biome format on staged files) + typecheck + lint
  pre-push    →  full typecheck + lint (safety net before push)
  turbo.json  →  cache: false for typecheck/lint (if applicable)

Errors will now be caught before they leave your machine.
```

</process>

<notes>
## Claude Code hooks
- The biome format hook uses `jq` to extract the file path from the tool input. This is standard on macOS (via Homebrew) and most Linux distros. If jq is missing, the hook silently fails (|| true).
- The Stop hook uses `xargs -r` which is a no-op if there are no files. On macOS, `xargs` without `-r` still works (just runs biome with no args, which exits cleanly).
- The context monitor hooks reference files inside the Arc plugin. If the user uninstalls Arc, these hooks will silently fail (node script not found → exit 1, but hooks don't block).
- `--unsafe` in the Stop hook enables Biome's unsafe fixes (like removing unused imports). This is intentional — at conversation end, we want maximum cleanup.
- The PostToolUse format hook handles both `file_path` (Edit tool) and `filePath` (NotebookEdit) via jq fallback.
- The tsc hook uses `tail -20` to avoid flooding the stop output — just enough to see if there are errors and what they are.
- The git guard uses PreToolUse with `decision: block` to stop destructive commands before execution. This is especially important for users running with `--dangerously-skip-permissions` or liberal auto-approve settings.
- The git guard blocks: `git reset --hard`, `git push --force` / `git push -f`, `git clean -f`, `git checkout .`. These are the operations that destroy uncommitted work with no undo.

## Git hooks
- Husky v9 uses a flat `.husky/` directory — hook files are directly in `.husky/pre-commit`, `.husky/pre-push`, etc. No `_` subdirectory or `husky.sh` sourcing needed.
- Vite+ uses `.vite-hooks/` with a `_/` subdirectory containing the hook runner. `git config core.hooksPath` is set to `.vite-hooks/_` by `vp config`. User hooks go directly in `.vite-hooks/pre-commit`, `.vite-hooks/pre-push`, etc.
- The pre-commit hook runs `pnpm lint-staged` first (fast — only formats staged files), then `pnpm typecheck && pnpm lint` (catches errors before commit).
- The pre-push hook runs the same typecheck + lint as a safety net. It uses `|| exit 1` instead of `&&` so each step reports its own failure.
- **Turborepo cache: false is the most important fix.** Without it, `turbo typecheck` in a git hook may return a cached success from a previous run, silently passing despite new type errors. This is the #1 cause of type errors getting through hooks.
- `cache: false` should ONLY be set on `typecheck`, `lint`, and `check-types` tasks. `build` and `test` tasks should keep their cache — those have real outputs and benefit from caching.
- Never remove project-specific pre-push steps (env validation, SDK guards, case-sensitivity checks, unit tests). Only add missing typecheck/lint steps.
</notes>

Related Skills

vision

16
from howells/arc

Create or review a high-level vision document capturing project goals and purpose. Use when asked to "define the vision", "what is this project", "set goals", or when starting a new project that needs clarity on purpose and direction.

using-arc

16
from howells/arc

Use when starting any conversation - establishes Arc's skill routing, instruction priority, and bootstrap rules

tidy

16
from howells/arc

Clean up completed plans in docs/arc/plans/. Archives or deletes finished plans. Use when asked to "clean up plans", "tidy the docs", "archive old plans", or after completing implementation to remove stale planning documents.

testing

16
from howells/arc

Comprehensive testing strategy. Creates test plans covering unit, integration, and E2E. Uses specialist agents for each test type. Supports vitest and Playwright with auth testing guidance for Clerk and WorkOS.

suggest

16
from howells/arc

Opinionated recommendations for what to work on next based on Linear issues, tasks, and codebase. Use when asked "what should I work on", "what's next", "suggest priorities", or when starting a session and unsure where to begin.

seo

16
from howells/arc

Deep SEO audit for web projects. Analyzes codebase for crawlability, indexability, on-page SEO, structured data, social previews, and technical foundations. Optionally runs Lighthouse and PageSpeed against a live URL. Reports findings with severity, offers direct fixes or /arc:detail plans. Use when asked to "audit SEO", "check SEO", "review SEO", or "is my site SEO-ready".

responsive

16
from howells/arc

Audit and fix responsive/mobile issues across every page of a project, using browser screenshots at two breakpoints (375px mobile, 1440px desktop). Design-aware: reads existing design docs to preserve aesthetic intent, not just "make it fit." Use when asked to "make it responsive", "fix mobile", "responsive audit", or after building a desktop-first UI that needs mobile adaptation.

refactor

16
from howells/arc

Discover architectural friction and propose structural refactors with competing interface designs. Focuses on deepening shallow modules, consolidating coupled code, and improving testability. Use when asked to "improve the architecture", "find refactoring opportunities", "deepen modules", "consolidate coupling", "make this more testable", or "find architectural friction".

prune-agents

16
from howells/arc

Kill orphaned Claude subagent processes that didn't exit cleanly. Use when asked to "prune agents", "clean up agents", "kill orphaned processes", or when subagents accumulate from Task tool usage.

progress

16
from howells/arc

Internal skill for progress journal management. Other skills append to docs/arc/progress.md for cross-session context. Not invoked directly by users.

naming

16
from howells/arc

Generate and validate project names. Reads codebase context, produces candidates using tech naming strategies, and checks domain + GitHub availability. Use when naming a new project, renaming, or validating an existing name.

letsgo

16
from howells/arc

Production readiness checklist covering domains, SEO, security, and deployment. Use when asked to "ship it", "deploy to production", "go live", "launch", or when preparing a project for production deployment.