[FEATURE] Session-scoped state for hooks (follow-up to #4321)
Summary
This is a follow-up to #4321 which was auto-closed due to inactivity. The need for session-scoped state sharing between hooks remains valid.
Note: This is distinct from #18654 which proposes agent-accessible session variables. This feature request is specifically for hook-private state that the agent cannot see or modify - only hooks can read/write it.
The Problem
Hooks are stateless between invocations. Any hook needing to maintain state across multiple tool calls must use external mechanisms. The naive approach of writing to a shared file (e.g., /tmp/hook-state.json) breaks with concurrent Claude sessions since multiple sessions would corrupt each other's state.
Why Hook-Private State?
#18654 proposes CLAUDE_SESSION_VARS that both the agent and hooks can access. This feature request is for separate, hook-only state because:
- Security tracking: Count blocked command attempts without informing the agent
- Internal metrics: Track hook execution stats without polluting agent context
- Implementation details: Coordinate between PreToolUse/PostToolUse without exposing internals
- Separation of concerns: Hook coordination logic shouldn't be visible to the agent
Current Workaround
The session_id field is already provided in hook input, so users can implement session-isolated state files:
import json, os
STATE_DIR = f"/run/user/{os.getuid()}/claude-hooks"
os.makedirs(STATE_DIR, exist_ok=True)
data = json.load(sys.stdin)
state_file = f"{STATE_DIR}/{data['session_id']}.json"
# Load session-specific state
state = json.load(open(state_file)) if os.path.exists(state_file) else {}
# Update and save
state["my_key"] = "my_value"
json.dump(state, open(state_file, "w"))
This works but has drawbacks:
- Every hook author must implement this boilerplate
- File I/O overhead on every hook invocation
- No cleanup mechanism when sessions end (stale files accumulate)
- Race conditions possible with parallel hook execution
Proposed Solution
Extend the hook contract as originally proposed in #4321:
1. Add hook_state to hook input (invisible to agent):
{
"session_id": "abc123",
"hook_event_name": "PreToolUse",
"hook_state": {
"blocked_rm_count": 3,
"last_tool_duration_ms": 1250
}
}
2. Allow hooks to update state via output:
{
"update_hook_state": {
"blocked_rm_count": 4
}
}
Prior Art
- Claude-Mem implements this pattern using a database keyed by
session_id - claude-code-hooks-mastery uses file-based logging but lacks session isolation
Use Cases
- Track metrics per session (tool usage counts, timing) without agent awareness
- Security: count/log blocked attempts invisibly
- Coordinate between PreToolUse and PostToolUse hooks
- Rate limiting / throttling within a session
Related
- #4321 - Original proposal (auto-closed)
- #18654 - Agent-accessible session variables (complementary, not duplicate)
- #23386 - Expose background tasks to hooks (related but narrower)
🤖 Generated with Claude Code
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗