{"id":"cc-hooks","name":"cc-hooks","summary":"デフォルトのClaudeコード強制フックとオプトインガードレシピを設定しましょう。トリガー:「cc-hooks」、「Claude Code hooksの設定」、「install hooks」。","body":"# Claude Code Hooks\n\nShell commands that fire at specific points in Claude Code's lifecycle.\n\nHooks enforce mechanically what prose cannot: a model can reason its way past\nan instruction, but it cannot reason its way past an exit 2 — which is exactly\nwhy every hook must be narrow, silent, and reversible.\n\nNamed failure mode — **chatty happy path**: a hook that emits stdout on exit 0\ncorrupts the tool call it was guarding; silence on success is part of the\ncontract, not a style preference.\n\n## Constraints\n\n- Enforcement hooks (the PreToolUse policy dispatcher) ship by DEFAULT: plugin installs auto-wire `hooks/hooks.json`; skill copies and checkouts wire with one command (`scripts/install-hooks.sh`). Operators can disable per host (`/plugin disable`, or remove the settings matchers).\n- Injection hooks (SessionStart/UserPromptSubmit context stuffing) stay dead — the #511 teardown proved delta=0 at 10.35M resident tokens. Never ship one; the hookless-cold-start gate still enforces this.\n- Keep the happy path silent and block only with the event's documented exit/JSON contract because stray stdout can corrupt a tool call.\n- Bound Stop hooks with `stop_hook_active` and scope matchers narrowly to prevent recursion and unrelated-command interception.\n\n<!-- TOC: Quick Start | Events | Blocking | Writing Hooks | Anti-Patterns | References -->\n\n## Quick Start\n\nAdd to `~/.claude/settings.json` (user) or `.claude/settings.json` (project):\n\n```json\n{\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Bash\",\"hooks\":[{\"type\":\"command\",\"command\":\"my-validator.sh\"}]}]}}\n```\n\n## Hook Events\n\n| Event | When | Blocks? | Common Use |\n|-------|------|---------|------------|\n| `PreToolUse` | Before tool runs | Yes | Block/modify commands |\n| `PostToolUse` | After tool succeeds | Feedback | Auto-format, lint |\n| `PermissionRequest` | Permission dialog | Yes | Auto-approve/deny |\n| `UserPromptSubmit` | Prompt submitted | Yes | Add context, validate |\n| `Stop` | Claude finishes | Yes | Force continue |\n| `SessionStart` | Session begins | No | Load context, set env |\n| `Notification` | Notifications | No | Desktop alerts |\n\nFull schemas: [HOOK-EVENTS.md](references/HOOK-EVENTS.md)\n\n## Matchers\n\n```\n\"Bash\"              → exact match\n\"Edit|Write\"        → regex OR\n\"mcp__.*__write\"    → MCP tools\n\"*\" or \"\"           → all tools\n```\n\nTools: `Bash`, `Read`, `Write`, `Edit`, `Glob`, `Grep`, `Task`, `WebFetch`, `WebSearch`\n\n## Exit Codes\n\n| Code | Effect |\n|------|--------|\n| 0 | Success - JSON parsed from stdout |\n| 2 | **Block** - stderr fed to Claude |\n| Other | Non-blocking error |\n\n## Blocking a Tool\n\n**Simple (exit 2):**\n```bash\necho \"Blocked: reason\" >&2 && exit 2\n```\n\n**JSON (exit 0):**\n```json\n{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"Blocked\"}}\n```\n\nDecisions: `\"allow\"` (auto-approve), `\"deny\"` (block), `\"ask\"` (show dialog)\n\n## Modifying Input\n\n```json\n{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\n  \"updatedInput\":{\"command\":\"modified-command\"}}}\n```\n\n## Real-World: DCG + RCH\n\n```json\n{\"hooks\":{\"PreToolUse\":[{\"matcher\":\"Bash\",\"hooks\":[\n  {\"type\":\"command\",\"command\":\"dcg\"},\n  {\"type\":\"command\",\"command\":\"rch\"}\n]}]}}\n```\n\n- **DCG**: Blocks `git reset --hard`, `rm -rf`, `git push --force`\n- **RCH**: Routes builds to remote workers\n\nDetails: [DCG-RCH.md](references/DCG-RCH.md)\n\n## Skill-First Coordination Guard (opt-in)\n\nA copy-paste PreToolUse recipe that nudges agents to **load the coordination\nskill before hand-rolling the `am`/`atm`/`ntm`/`tmux send-keys` CLI**. This\nrecipe auto-installs nothing; you opt in per host (unlike the policy\ndispatcher, which ships by default).\n\n**Context-budget doctrine for hooks:** hooks are the most powerful enforcement\n(mechanical, can't be reasoned past) but they pollute context — use sparingly. A\nhook must be SILENT on the happy path (exit 0, no stdout/stderr), fire ONLY on a\nreal violation (ideally once per session, sentinel-gated), prefer PreToolUse\nviolation-guards over `UserPromptSubmit`/`SessionStart` per-turn injectors, and\nNEVER emit stray stdout on an exit-0 PreToolUse path (it is parsed as JSON and\nbreaks the tool call). Block via exit 2 + stderr.\n\nThe recipe ships both scripts verbatim, a precise head-only matcher (so a\n`br create --body \"...am/atm/ntm...\"` never false-fires), the two-matcher\nopt-in `settings.json` snippet, and a bats test proving every fire/silent case.\n\nRecipe: [SKILL-FIRST-COORDINATION-GUARD.md](references/SKILL-FIRST-COORDINATION-GUARD.md)\n\n## Installed-Skill-Edit Guard (opt-in)\n\nA PreToolUse `Edit|Write` guard that routes an edit of an **installed skill copy**\n(`*/.claude/skills/**`, `.codex`, `.gemini`) back to the repo source of truth\n`skills/<name>/`. This is a TRUE mistake-token — editing an installed/symlinked\ncopy has no legitimate form (overwritten on install, or symlinks through to the\nfactory checkout). Zero false-positive surface: it matches `tool_input.file_path`\nonly, so a doc that merely mentions `claude/skills` in its body never fires.\nReversible → it ROUTES (exit 2 + one-line redirect), not hard-blocks. Silent on\nevery other path; fires once per session. Ships INERT — opt-in installer:\n\n```bash\nscripts/install-installed-skill-edit-guard.sh   # user scope; --project for project\n```\n\nRecipe: [INSTALLED-SKILL-EDIT-GUARD.md](references/INSTALLED-SKILL-EDIT-GUARD.md)\n\n### Value-proof (why this guard survives the hookless teardown)\n\nThe keystone guard ships **gate-blind per-fire telemetry**: on each fire it\nappends exactly one JSONL line — `{ts, session, token_class, path_sha256}` — to\n`${AGENTOPS_HOME:-~/.agents/ao}/guardrail-telemetry.jsonl` (override with\n`AGENTOPS_GUARDRAIL_TELEMETRY`). The path is **SHA-256 hashed, never raw**\n(privacy); nothing is written on the happy path; the sensor is inert until the\nguard is installed and fires. The pre-registered methodology — metric =\ndeclining fire-ATTEMPT rate over time (a signal the redirect cannot fake, NOT the\ncircular hand-roll rate), minimum N, noise floor, and **null-at-small-N is an\nacceptable outcome** — satisfies ADR-0002 l.58 (\"test or eval evidence showing\npositive value\"), the criterion whose absence killed 2.x hooks (#511).\n\nMethodology: [GUARDRAIL-VALUE-PROOF.md](references/GUARDRAIL-VALUE-PROOF.md)\n\n## Policy Dispatch Engine (ships by default)\n\nThe admission-control layer (epic age-4qw1): **one** PreToolUse dispatcher —\n[hooks/policy-dispatch.sh](hooks/policy-dispatch.sh) — evaluating a\n**policies-as-data** registry\n([policies/policies.json](policies/policies.json), contract\n`schemas/hooks-manifest.v2.schema.json`) instead of N hand-wired settings\nentries. This is the membrane at tool-call altitude: same vocabulary, lower\naltitude than the pawl/gate at push time.\n\nPer policy: dcg-style id (`domain.object:token`), `mode: deny | route | audit`,\nmatchers (tool + `command`/`file_path` regex), a `route_message` that names THE\ncorrect tool, a rationale, and a pre-registered `value_proof` (the ADR-0002\nlease-on-life: no proof accruing → retire the policy).\n\n**Predicate discipline, schema-enforced** (the #511 anti-lesson): only\n`predicate_class: pure` — syntactic mistake-tokens over the command or file\npath — may `deny`/`route`. Lookup/stateful predicates ship `audit`-only until\npromoted with reviewed fires.\n[scripts/lint-policies.sh](scripts/lint-policies.sh) enforces this mechanically\n(jq-only; runs in bats and CI).\n\n**Accepted false-positive surface:** because a pure predicate matches its token\nanywhere in the raw command string, a protected token quoted as *data* (a commit\nmessage body, a `dcg test \"...\"` probe, a here-doc payload) can still fire even\nthough nothing harmful would run. This is the deliberate cost of the\npure-only-may-deny rule — the alternative (repo/context lookups) is exactly the\nstateful predicate the discipline bars from `deny`. Every fire is reversible: a\none-shot `AOP_WAIVE=<policy-id>` or a `policy-waivers` line clears it.\n\nSemantics: happy path = exit 0, zero output. `deny` = exit 2 + one stderr\nroute line (full message once per session, short line after — every attempt\nstill blocks). `route` = exit 0 + `permissionDecision:\"ask\"` JSON. `audit` =\nallow + record. Every fire appends one hashed guardrail-telemetry line\n(`token_class` = policy id, plus `mode`/`decision`). Waive once with\n`AOP_WAIVE=<policy-id>`, or a `policy-waivers` file line\n`<policy-id> <expiry-epoch>`. Missing registry or jq fails OPEN.\n\nDay-1 enforce cohort (age-wnyt, all pure-regex, high-pain):\n\n| Policy | Blocks | Routes to |\n|---|---|---|\n| `core.git:add-beads-ledger` | `git add` naming `_beads/` (private ledger leak is one-way) | push the ledger repo itself — never `git add _beads` in the public tree |\n| `core.provenance:ledger-hand-append` | redirect/`tee`/Edit/Write onto `docs/provenance/ledger.jsonl` (hash-chained, sealed) | `ao provenance add` |\n| `core.skills:copy-into-installed` | `cp`/`rsync`/`mv` INTO `~/.claude|.codex|.gemini/skills` (dest-position enforced) | `ao skills link` |\n| `core.skills:edit-installed-copy` | Edit/Write of an installed skill copy (`file_path` only — prose can never fire it) | edit repo `skills/<name>/` |\n\n**How it reaches users — every install path delivers hooks:**\n\n| Install path | Delivery |\n|---|---|\n| Claude Code plugin (`claude plugin install agentops@agentops-marketplace`) | **Automatic** — the plugin bundles `hooks/hooks.json` (`${CLAUDE_PLUGIN_ROOT}` paths); hooks are active on install, no wiring step |\n| `npx skills@latest add boshu2/agentops` / skills.sh copy | The skill package carries its own installer: `~/.claude/skills/cc-hooks/scripts/install-hooks.sh` (one command; file copies cannot self-wire) |\n| git clone / brew checkout | `scripts/install-policy-dispatch.sh` (delegates to the same skill-embedded installer) |\n\nThe installer lints the registry before wiring, backs up settings, and is\nidempotent. Disable per host with `/plugin disable agentops` or by removing the\ntwo PreToolUse matchers from settings.\n\nContract tests: `tests/scripts/policy-dispatch.bats` (block+message+telemetry\nper policy, stray-stdout hazard, waivers, audit/route modes, fail-open).\n\n## Writing Your Own Hook\n\n**Minimal Python:**\n```python\n#!/usr/bin/env python3\nimport json, sys\n\ndata = json.load(sys.stdin)\ncmd = data.get('tool_input', {}).get('command', '')\n\nif 'dangerous' in cmd:\n    print(\"Blocked: dangerous\", file=sys.stderr)\n    sys.exit(2)\n\nsys.exit(0)  # Allow\n```\n\n**Hook input (stdin):**\n```json\n{\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"npm test\"},\"session_id\":\"...\",\"cwd\":\"...\"}\n```\n\n## Environment Variables\n\n| Variable | Scope | Purpose |\n|----------|-------|---------|\n| `CLAUDE_PROJECT_DIR` | All | Project root |\n| `CLAUDE_ENV_FILE` | SessionStart/Setup | Persist env vars |\n\n## Stop Hook (Force Continue)\n\n```json\n{\"decision\":\"block\",\"reason\":\"Tests failing. Fix before stopping.\"}\n```\n\n**Critical:** Check `stop_hook_active` to prevent infinite loops.\n\n## Anti-Patterns\n\n| Don't | Do |\n|-------|-----|\n| Old object format | Array format with `matcher` |\n| Unquoted `$VAR` | `\"$VAR\"` |\n| Exit 2 with JSON | Exit 2 uses stderr only |\n| Skip `stop_hook_active` check | Always check in Stop hooks |\n\n## Debugging\n\n```bash\nclaude --debug  # Hook execution details\n/hooks          # View/edit in REPL\n```\n\n## Output Specification\n\n- **Path:** user `~/.claude/settings.json` or project `.claude/settings.json`, plus explicitly named hook scripts. The PreToolUse policy dispatcher ships by default (every install path wires it — see \"Policy Dispatch Engine\"); the additional guard recipes (skill-first coordination, standalone installed-skill-edit) stay inert until opted in.\n- **Filename:** preserve `settings.json`; give scripts descriptive executable filenames rather than embedding large shell programs in JSON.\n- **Format:** valid Claude hook JSON using event arrays, matchers, and command objects; hook stdout/stderr and exit codes follow the selected event schema.\n- **Exit code:** validate with `jq -e '.hooks | type==\"object\"' <settings.json>` and a representative silent/fire test for each matcher; any parse error, noisy happy path, or recursion risk blocks activation.\n- **Downstream handoff:** consumed by the operator only after the exact scope, reversal command, test evidence, and opt-in location are reported.\n\n## Quality Checklist\n\n- The matcher fires on the intended event/input and stays silent on representative near misses.\n- Blocking and allow paths use the documented exit code and output channel without leaking context.\n- The hook is reversible, narrowly scoped, recursion-safe, and clearly labeled as opt-in host policy.\n\n## References\n\n- [HOOK-EVENTS.md](references/HOOK-EVENTS.md) - All events with full schemas\n- [DCG-RCH.md](references/DCG-RCH.md) - Production examples (dcg, rch)\n- [INSTALLED-SKILL-EDIT-GUARD.md](references/INSTALLED-SKILL-EDIT-GUARD.md) - Opt-in guard routing installed-skill edits to repo skills/ (keystone)\n- [GUARDRAIL-VALUE-PROOF.md](references/GUARDRAIL-VALUE-PROOF.md) - Pre-registered value-proof methodology + per-fire telemetry contract (ADR-0002 l.58)\n- [PATTERNS.md](references/PATTERNS.md) - Auto-format, logging, notifications\n- [JSON-OUTPUT.md](references/JSON-OUTPUT.md) - Response schemas","author":"@boshu2","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/boshu2/agentops/tree/main/images/gemini/skills/cc-hooks","license":"Apache-2.0","category":"writing","lang":"en","tokens":3427,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":[]}}