Skip to content
Back to skills

Writing Hooks

ASecurity

Guide for writing, configuring, and debugging Claude Code hooks. Use when adding, writing, or debugging hooks — including silent failures and permission errors.

  • 34 stars
  • 0 votes
  • 0 copies
  • 8 views
  • Added September 12, 2026
ai-agentspythongobashnodeexpressdebugging

Works with

  • claude code

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add tim-hub/powerball-harness --skill writing-hooks --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Writing Hooks?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Writing Hooks
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tim-hub-writing-hooks/badge)](https://www.skillsdirectory.com/skills/tim-hub-writing-hooks)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: writing-hooks
description: "Guide for writing, configuring, and debugging Claude Code hooks. Use when adding, writing, or debugging hooks — including silent failures and permission errors."
when_to_use: "write hook, create hook, hook not running, debug hook, hook silent failure, hooks.json, pre-tool hook, post-tool hook"
allowed-tools: ["Read", "Write", "Edit", "Bash"]
---

# Writing Hooks

Guide for adding hooks to `harness/hooks/hooks.json` and writing the scripts they invoke.

## Hook Types

| Type | When to use | Cost |
|------|-------------|------|
| `command` | Deterministic checks, fast guards, file ops | Low |
| `prompt` | Single LLM judgment with `{"ok": true/false}` response | Medium |
| `agent` | Multi-step LLM reasoning with file tools | High |
| `http` | POST to external service | Varies |

Prefer `command` whenever the logic can be expressed in any executable script or the Go binary. Reserve `agent`/`prompt` for cases that genuinely need judgment — they are slower and cost tokens on every invocation.

> **Note**: `agent` hooks do NOT work on `PreCompact`. Use `command` instead.

## Adding a command Hook

### 1. Write the script

Place scripts in `harness/scripts/`. Use whichever language fits the task — bash, Python, and Node.js are all fine. The only requirement is that the script is executable and produces the correct exit code and stdout.

**Exit codes control the hook outcome:**

| Exit code | Meaning |
|-----------|---------|
| `0` | Allow / pass |
| `2` + JSON to stdout | Block (PreToolUse / Stop) |
| `0` + JSON to stdout | Inject context (PostToolUse, PreCompact) |

**Bash example — blocking Stop hook:**
```bash
#!/usr/bin/env bash
if [[ some_condition ]]; then
  echo '{"decision":"block","reason":"Explain why"}'
  exit 2
fi
exit 0
```

**Python example — blocking Stop hook:**
```python
#!/usr/bin/env python3
import sys, json
if some_condition:
    print(json.dumps({"decision": "block", "reason": "Explain why"}))
    sys.exit(2)
sys.exit(0)
```

**Node.js example — warning injection (PreCompact):**
```js
#!/usr/bin/env node
if (someCondition) {
  console.log(JSON.stringify({ systemMessage: "Warning: something needs attention" }));
}
process.exit(0);  // never block compaction
```

### 2. Set execution permission

Scripts without `chmod +x` fail silently or with `permission denied`, regardless of language:

```bash
chmod +x harness/scripts/your-script.py   # or .sh, .js, etc.
```

Alternatively, invoke the interpreter explicitly in the hook command (step 3) — then `chmod +x` is not required.

### 3. Register in hooks.json

Edit `harness/hooks/hooks.json` (SSOT). Reference the script via `${CLAUDE_PLUGIN_ROOT}` and invoke with the appropriate interpreter:

```json
{ "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/your-script.sh\"", "timeout": 5 }
{ "type": "command", "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/scripts/your-script.py\"", "timeout": 5 }
{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/your-script.js\"", "timeout": 5 }
```

For `bin/harness` subcommands, use `timeout: 3`. For scripts, use `timeout: 5`.

### 4. Validate

```bash
make validate   # runs tests/validate-plugin.sh
```

---

## Checklist for New Hooks

- [ ] Script has the correct shebang for its language (`#!/usr/bin/env bash`, `#!/usr/bin/env python3`, `#!/usr/bin/env node`)
- [ ] Script is executable (`chmod +x`) **or** interpreter is explicit in the hook command (`python3 "..."`, `node "..."`)
- [ ] Script path uses `${CLAUDE_PLUGIN_ROOT}/scripts/` — never absolute paths
- [ ] Timeout matches type: `3` for `bin/harness`, `5` for scripts, `30` for agents
- [ ] Tested locally by running the script directly with its interpreter
- [ ] `make validate` passes after editing `hooks.json`

---

## Common Issues

### Hook not running (silent failure)

Three likely causes:

1. **Not executable** — check with `ls -la harness/scripts/your-script.*`. Either `chmod +x` the file or prefix the command with the interpreter (`python3 "..."`, `node "..."`, `bash "..."`)
2. **Wrong path** — verify the path resolves correctly; run the script directly to confirm
3. **Syntax error** — run the script manually (`python3 script.py`, `node script.js`, `bash -n script.sh`) to surface errors before the hook fires

### Permission denied

```bash
chmod +x harness/scripts/your-script.py   # or .sh, .js
# OR use explicit interpreter in hooks.json — no chmod needed
```

### Hook fires but doesn't block

For blocking hooks (PreToolUse / Stop), you must **both** print JSON to stdout **and** `exit 2`. Printing alone or exiting alone won't block.

### agent hook not firing on PreCompact

Agent hooks silently do nothing on `PreCompact`. Convert to a `command` script — see `harness/scripts/check-wip-precompact.sh` as a reference.

---

## Event Reference

Key events and their typical use:

| Event | Use for |
|-------|---------|
| `PreToolUse` | Guard writes, validate inputs before execution |
| `PostToolUse` | Cleanup, logging, tracking after tool runs |
| `Stop` | Block session end if work is incomplete |
| `PreCompact` | Warn (don't block) before context compaction |
| `UserPromptSubmit` | Inject policy context, track commands |
| `SessionStart` | One-time session initialization (`once: true`) |

Full event list and response schemas: `.claude/rules/hooks-editing.md`

---

## Related

- `harness/hooks/hooks.json` — SSOT for all hook configuration
- `harness/scripts/` — All command hook scripts
- `.claude/rules/hooks-editing.md` — Complete event reference and timeout guidelines
- `update-config` skill — For adding automated behaviors via settings.json

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.