Hook Events
AgentiHooks registers handlers for all 10 Claude Code hook events. StatusLine is not a hook event – it is a native Claude Code setting ("statusLine" key in settings.json) handled by a separate script (hooks/statusline.py).
Table of contents
- Exit code semantics
- SessionStart
- SessionEnd
- UserPromptSubmit
- PreToolUse
- PostToolUse
- Stop
- SubagentStop
- StatusLine (native setting, not a hook event)
- Notification
- PreCompact
- PermissionRequest
- Token Control Layer
- Tool memory learning
Exit code semantics
| Exit code | Meaning |
|---|---|
0 | Allow – Claude Code proceeds normally |
2 | Block – Claude Code cancels the action and displays the hook’s stderr as a warning |
Note:
BlockActionexceptions print to stderr (not stdout). This ensures Claude Code displays the block reason cleanly rather than showing “No stderr output”.
SessionStart
When: A new Claude Code session begins.
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Unique session identifier |
Handler actions:
- Creates
/tmp/<session_id>/as the session working directory - Injects a context message into Claude’s context window with session awareness
- Logs output token limit awareness if
CLAUDE_CODE_MAX_OUTPUT_TOKENSis set - If
MCP_HYGIENE_ENABLED=true: injects a reminder to disable unused MCP servers via/mcpto reduce per-turn token overhead - If
BROADCAST_ENABLED=true: registers session in active-sessions.json and delivers any pending one-shot broadcasts
SessionEnd
When: The session ends normally (not via Stop).
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Session identifier |
transcript_path | string | Path to the session transcript JSONL file |
Handler actions:
- Parses the transcript to extract metrics (
num_turns,duration_ms) - Logs all transcript entries to the hooks log
- If
FILE_READ_CACHE_ENABLED=true: clears the file read cache for this session from Redis - If
CONTEXT_REFRESH_ENABLED=true: clears the turn counter state (Redis key + file) - If
BROADCAST_ENABLED=true: deregisters session from active-sessions.json - Cleans up the
/tmp/<session_id>/directory
UserPromptSubmit
When: The user submits a prompt (before Claude processes it).
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Session identifier |
prompt | string | The user’s raw prompt text |
Handler actions:
- Scans the prompt for secrets and credentials using regex patterns
- If secrets are detected: injects a warning into the context (does not block – warnings only at this stage)
- If
CONTEXT_REFRESH_ENABLED=true: increments a per-session turn counter and:- Every
CONTEXT_REFRESH_INTERVALturns (default 20): re-injects rules files sorted by frontmatterpriority: N(lower = higher priority). Project rules dir resolved from the hook payload’scwdfield (the session’s active directory) - Every
CONTEXT_REFRESH_CLAUDE_MD_INTERVALturns (default 40): re-injects~/.claude/CLAUDE.md(and optionally projectCLAUDE.md) - Both combat attention decay in long sessions where early-loaded instructions lose influence
- Content is compressed via the Context Preprocessor (default level:
standard; setCONTEXT_REFRESH_COMPRESSION=offto disable)
- Every
- If
BROADCAST_ENABLED=true: checks for undelivered broadcasts and injects them as banners. Critical+persistent broadcasts are injected on every turn; info broadcasts are delivered once per session.
PreToolUse
When: Before any tool executes. This is the primary security gate.
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Session identifier |
tool_name | string | Name of the tool about to run |
tool_input | object | Tool input parameters |
transcript_path | string | Path to transcript |
Handler actions:
- Logs the transcript entry
- Secret scanning – scans
tool_inputfor credentials; exits with code2(block) if found - File read deduplication – if
FILE_READ_CACHE_ENABLED=trueandtool_name == "Read": checks whether the file was already read this session and is unmodified (by mtime). If so, exits with code2and tells Claude to use the content already in context - CLAUDE.md sanity check – if
AGENTIHOOKS_CLAUDE_MD_SANITY_CHECK=true(default) andtool_nameisWriteorEdittargeting aCLAUDE.mdorCLAUDE.local.mdfile: simulates the resulting file and exits with code2if it would exceedAGENTIHOOKS_CLAUDE_MD_MAXLINES(default200). Prevents agents from bloating critical config files - Tool memory injection – looks up past errors for this tool and injects them as context so the agent can avoid repeating mistakes
- Version guard – if
tool_nameisWriteorEdittargeting a version-managed manifest (pyproject.toml,package.json, etc.): blocks version field modifications (version bumping must go through the release workflow) - If
BROADCAST_ENABLED=trueandBROADCAST_CRITICAL_ON_PRETOOL=true: checks for critical+persistent broadcasts and injects them viaadditionalContextJSON. This ensures the agent sees critical messages before every tool call, not just at the start of each turn.
Exit codes used:
0– tool is safe to run2– secret detected or redundant file read blocked; action blocked with explanation
PostToolUse
When: After a tool completes (success or failure).
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Session identifier |
tool_name | string | Name of the tool that ran |
transcript_path | string | Path to transcript |
tool_output | string | Tool’s stdout |
tool_error | string | Tool’s stderr (empty on success) |
Handler actions:
- Logs the transcript entry
- If
BASH_FILTER_ENABLED=trueandtool_name == "Bash": detects verbose output categories (docker logs, kubectl, git log, test runners, build tools) and truncates to configured limits before it accumulates in the context window. Filtered output is re-emitted viaadditionalContextso Claude still sees the relevant portion - If
FILE_READ_CACHE_ENABLED=trueandtool_name == "Read": records the file path and its current mtime in the session cache (Redis or memory) so future re-reads can be detected - If
tool_erroris non-empty: records the error pattern to the tool memory file (~/.agenticore_tool_memory.ndjson) for future injection
Stop
When: The agent stops (task complete or unrecoverable error). This is the most active handler.
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Session identifier |
transcript_path | string | Path to transcript |
Handler actions:
- Parses transcript to extract metrics (
num_turns,duration_ms) - Scans transcript for MCP errors that
PostToolUsemay have missed - If errors found and email is configured (
SMTP_SERVER): sends an error notification email - Logs all transcript entries
- If
MEMORY_AUTO_SAVE=true: saves a session digest to the memory store
SubagentStop
When: A subagent (spawned agent) stops.
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Subagent’s session identifier |
transcript_path | string | Path to subagent transcript |
Handler actions:
- Logs the subagent’s transcript entries to the hooks log
StatusLine (native setting, not a hook event)
StatusLine is not a hook event. It is configured as a native Claude Code setting in settings.json:
"statusLine": {
"type": "command",
"command": "cd /app && python3 -m hooks.statusline"
}
Claude Code pipes a JSON payload to hooks/statusline.py on every turn. The script reads the payload and prints 2-3 lines to stdout that appear in the terminal status bar.
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Session identifier |
context_window | object | {context_window_size, total_input_tokens, used_percentage, current_usage, ...} |
model | object | {display_name, ...} – active model |
cost | object | {total_cost_usd, total_duration_ms, total_api_duration_ms, total_lines_added, total_lines_removed} |
worktree | object | Active worktree info (name, path) – if in a worktree |
vim | object | Vim mode info ({mode}) – if vim keybindings are active |
Output (printed to stdout):
Line 1 – context fill bar, model, cost, duration:
########## 54% | claude-sonnet-4-6 | $0.0231 | 12s
Line 2 – token counts, burn rate, lines changed, cache ratio, git branch:
ctx: 540K/1M | burn: 23K/turn | +12-3 | cache: 67% | main
Line 3 (conditional) – threshold warning if fill % crosses TOKEN_WARN_PCT or TOKEN_CRITICAL_PCT, native rate limits, or peak/off-peak indicator:
CONTEXT 61% -- consider /compact soon
Key implementation detail: used_pct is recomputed from total_input_tokens / context_window_size * 100 rather than using the payload’s used_percentage field, which can carry stale values from the previous session’s final state.
Threshold warnings are edge-triggered: each level fires at most once per session (tracked in Redis at agenticore:token_warn:{session_id}). When Redis is unavailable, warnings fire every time the threshold is exceeded.
Burn rate is computed as the delta in total_input_tokens vs the previous turn’s value stored in Redis at agenticore:tokens:{session_id}. When Redis is unavailable, burn rate is omitted.
Configure via TOKEN_CONTROL_ENABLED, TOKEN_MONITOR_ENABLED, TOKEN_WARN_PCT, TOKEN_CRITICAL_PCT.
Notification
When: Claude Code sends a notification (e.g., requesting user attention).
Payload fields: varies – the entire notification data object.
Handler actions:
- Logs the notification event and payload
PreCompact
When: Claude Code is about to compact the context window.
Payload fields:
| Field | Type | Description |
|---|---|---|
session_id | string | Session identifier |
Handler actions:
- Logs a pre-compaction event marker
PermissionRequest
When: Claude Code requests permission for an action that requires user approval.
Payload fields:
| Field | Type | Description |
|---|---|---|
tool_name | string | Tool requesting permission |
| (other fields) | varies | Permission metadata |
Handler actions:
- Logs the permission request and tool name
Token Control Layer
PreToolUse, PostToolUse, SessionStart, SessionEnd, and the native StatusLine script work together to reduce context window consumption:
sequenceDiagram
participant Agent
participant SessionStart
participant StatusLine as StatusLine<br/>(native setting)
participant PreToolUse
participant PostToolUse
participant SessionEnd
participant Redis
SessionStart->>Agent: MCP hygiene reminder
loop Each turn
StatusLine->>Redis: read prev used tokens
StatusLine->>Agent: emit 2-line status bar (fill%, burn rate, cost, git)
StatusLine->>Agent: inject warning line (first threshold crossing only)
Agent->>PreToolUse: about to Read file.py
PreToolUse->>Redis: was file.py read + unmodified?
Redis-->>PreToolUse: yes -> BlockAction (exit 2 via stderr)
PreToolUse-->>Agent: "already in context, use it"
Agent->>PostToolUse: Bash docker logs ... output
PostToolUse->>Agent: re-emit truncated output via additionalContext
PostToolUse->>Redis: mark file.py as read (mtime stored)
end
SessionEnd->>Redis: delete file_cache + file_mtime keys
Configure via the Token Control environment variables.
Tool memory learning
PreToolUse and PostToolUse work together to implement cross-session error learning:
sequenceDiagram
participant Agent
participant PreToolUse
participant ToolMemory as Tool Memory<br/>(.ndjson)
participant PostToolUse
Agent->>PreToolUse: about to call tool X
PreToolUse->>ToolMemory: look up past errors for tool X
ToolMemory-->>PreToolUse: last N error patterns
PreToolUse-->>Agent: inject error patterns as context
Agent->>PostToolUse: tool X returned error Y
PostToolUse->>ToolMemory: record error Y for tool X
Configure via:
| Variable | Default | Description |
|---|---|---|
AGENTICORE_TOOL_MEMORY_PATH | ~/.agenticore_tool_memory.ndjson | Memory file path |
AGENTICORE_TOOL_MEMORY_MAX | 100 | Maximum stored entries |
AGENTICORE_TOOL_MEMORY_SHOW | 15 | Entries injected per PreToolUse |