# 140 Claude A7982489

> Hooks

- Skill: `tools-only/140-claude-a7982489` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add tools-only/140-claude-a7982489`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tools-only/140-claude-a7982489/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: tools-only (https://skillmd.com/u/tools-only)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tools-only/140-claude-a7982489

---

# Hooks

Python scripts that respond to Claude Code lifecycle events.

## Structure

```
hooks/
  hooks.json         # Auto-discovered config (events → scripts)
  hook_utils.py      # Shared utilities (ALWAYS import)
  {hook_name}.py     # Individual hook implementations
```

## Template

Every hook follows this structure:

```python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""
hook_name.py - Brief description.

Hook type: {PreToolUse|PostToolUse|SessionStart|UserPromptSubmit|etc.}
"""

from __future__ import annotations
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))

from hook_utils import (
    hook_main,
    log_debug,
    output_empty,
    parse_hook_input,
    read_stdin_safe,
)

@hook_main("HookEventName")
def main() -> None:
    raw = read_stdin_safe()
    data = parse_hook_input(raw)

    if not data:
        output_empty()
        return

    # Hook logic here

if __name__ == "__main__":
    main()
```

## Input/Output

| Direction | Format | Access |
|-----------|--------|--------|
| Input | JSON from stdin | `read_stdin_safe()` → `parse_hook_input()` |
| Output | JSON to stdout | `output_empty()`, `output_context()`, `output_block()` |
| Debug | stderr | `log_debug()` (only when `HOOK_DEBUG=1`) |

### Available Input Fields

Hook input JSON includes (varies by hook type):

| Field | Description |
|-------|-------------|
| `prompt` | User's message (UserPromptSubmit) |
| `cwd` | Current working directory |
| `permission_mode` | "plan" when in plan mode, empty otherwise |
| `tool_name` | Tool being used (PreToolUse, PostToolUse) |
| `tool_input` | Tool parameters (PreToolUse, PostToolUse) |

Example: Detecting plan mode
```python
permission_mode = data.get("permission_mode", "")
if permission_mode == "plan":
    # Handle plan mode differently
```

## Output Functions

| Function | When | Effect |
|----------|------|--------|
| `output_empty()` | Pass through | No modification |
| `output_context(event, ctx)` | Add context | Injects into conversation |
| `output_block(event, reason)` | Block action | Prevents tool execution |
| `output_permission(decision)` | Permission hooks | `allow`, `deny`, or `ask` |

## Configuration

Environment variables for user customization:

```python
def get_threshold() -> int:
    return int(os.environ.get("OMC_THRESHOLD", "100"))

def is_enabled() -> bool:
    return os.environ.get("OMC_FEATURE", "1").lower() not in ("0", "false", "no")
```

## Anti-Patterns

- Don't skip `@hook_main` decorator - handles all error recovery
- Don't print to stdout except via output functions
- Don't block stdin indefinitely - use `read_stdin_safe()`
- Don't import external packages without adding to `dependencies`
- Don't modify hook_utils.py without updating all dependents

