agentleFS
d3v0psdand3v0psdan/claude-skills/skills/cc-create-hook/SKILL.md

cc-create-hook

Skill0 starsChanged 5 months agoMITView on GitHub

Create a project-level Claude Code hook. Use whenever the user wants to add, set up, wire up, or configure a hook — to run a script before or after a tool, validate or block a command, auto-run a linter or formatter on edits, load context at session start, or react to any Claude Code lifecycle event. Writes the hook config into .claude/settings.json and the script into .claude/hooks/. Trigger this even if the user just describes the behavior (e.g. "run prettier every time you edit a file") without saying the word "hook".

  • Deletes or force-pushes
  • Commits and pushes

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 0 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Start from this template (.claude/hooks/<name>.sh):

    line 53
  2. Very specific:

    Important: if .claude/settings.json already exists, read it first and merge — don't overwrite it.

    line 126
  3. Very specific:

    Run /hooks in Claude Code to see it listed (read-only browser — confirms it loaded and from which file).

    line 133
  4. Very specific:

    If it "does nothing", check: is the script executable?

    line 135
  5. Very specific:

    Prefer PostToolUse for automation (format/lint/test) and PreToolUse for guardrails (block/validate).

    line 143

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Create a Claude Code hook

A hook is a script that fires automatically at a fixed point in Claude Code's lifecycle. Two pieces, always:

  1. The wiring goes in .claude/settings.json (project-level, safe to commit).
  2. The script goes in .claude/hooks/.

Work through the 5 steps below in order. Ask the user only what you can't already infer.


Step 1 — Pick the event (when should it fire?)

Match the user's goal to one event. These five cover almost everything:

Want to…EventCan it block?
Run a linter/formatter/test after a file changesPostToolUseno (observe)
Block or validate a command/edit before it runsPreToolUseyes
Add context or vet the prompt the user just sentUserPromptSubmityes
Load context / set up env when a session startsSessionStartno
Run a check when Claude finishes a turnStopyes

Need something else (subagents, compaction, notifications, file watching, MCP, etc.)? See references/events.md for the full list of ~30 events.

Step 2 — Pick the matcher (what narrows it?)

The matcher filters when the event fires. What it matches depends on the event:

  • Tool events (PreToolUse, PostToolUse) match on tool name:
    • "Bash" — only Bash commands
    • "Edit|Write" — file edits or writes (pipe = "or")
    • "mcp__.*" — any MCP tool (this one's a regex)
    • "*" or omitted — everything
  • SessionStart matches on startup, resume, clear, or compact.
  • UserPromptSubmit and Stop take no matcher — they always fire.

To narrow a tool event further (e.g. only git push, only .ts files), add an if field to the handler in Step 4 using permission-rule syntax: "Bash(git push *)", "Edit(*.ts)". One rule per if; no &&/||.

Step 3 — Write the script in .claude/hooks/

Claude Code sends the event as JSON on stdin. Read it, do the work, then signal the result with an exit code:

  • exit 0 → success. (For PreToolUse/Stop/UserPromptSubmit, this means "no objection".)
  • exit 2 → block it. Whatever you print to stderr is shown to Claude as the reason.
  • any other code → non-blocking error, just logged.

Start from this template (.claude/hooks/<name>.sh):

#!/bin/bash
# Reads the event JSON from stdin.
input=$(cat)

# Pull out the field you care about (needs jq on PATH).
# For tool events, the tool's args live under .tool_input:
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')
command=$(echo "$input"  | jq -r '.tool_input.command   // empty')

# --- do your thing here ---

exit 0

Blocking example — a PreToolUse hook that refuses rm -rf:

#!/bin/bash
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // empty')

if echo "$command" | grep -q 'rm -rf'; then
  echo "Blocked: rm -rf is not allowed here." >&2
  exit 2
fi
exit 0

Auto-format example — a PostToolUse hook that runs prettier on the edited file:

#!/bin/bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // empty')

[ -n "$file_path" ] && npx prettier --write "$file_path"
exit 0

Make it executable:

chmod +x .claude/hooks/<name>.sh

Need finer control than exit codes (allow without prompting, rewrite the tool input, add context for Claude)? Print a JSON object to stdout on exit 0 instead. The schemas are in references/events.md.

Step 4 — Wire it into .claude/settings.json

The config nests three levels: event → matcher group → handlers. Use the ${CLAUDE_PROJECT_DIR} placeholder so the path works no matter where Claude is running, and pass args: [] (this runs the script directly, no quoting headaches).

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/<name>.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Important: if .claude/settings.json already exists, read it first and merge — don't overwrite it. Add your event key (or append to the array under an existing event key). If the file doesn't exist, create it with just the hooks block above.

Optional handler fields worth knowing: "if" (narrow filter, Step 2), "timeout" (seconds, default 600), "async": true (run in background, don't block Claude).

Step 5 — Verify

Tell the user:

  • Run /hooks in Claude Code to see it listed (read-only browser — confirms it loaded and from which file).
  • Edits to settings are picked up automatically; no restart needed.
  • If it "does nothing", check: is the script executable? is jq installed? does the matcher actually match the tool? Exit code 1 does not block — only exit 2 does.

Keep it minimal

  • One hook = one job. Don't cram multiple checks into one script; add separate handlers.
  • Reach for a hook only when something must happen deterministically at a lifecycle point. For "Claude should usually do X", a rule or CLAUDE.md is lighter.
  • Prefer PostToolUse for automation (format/lint/test) and PreToolUse for guardrails (block/validate).

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.