[FEATURE] Session-scoped state for hooks (follow-up to #4321)

Resolved 💬 3 comments Opened Feb 5, 2026 by Butanium Closed Mar 6, 2026

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

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

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗