[FEATURE] Add Tool Context and user options to Notification Hooks

Resolved 💬 3 comments Opened Nov 6, 2025 by Cordona Closed Jan 12, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

Notification hooks lack correlation information to identify which specific tool triggered the notification event. When multiple tools are scheduled, external systems cannot determine which tool the notification is about because Claude executes tools in non-deterministic order.

Current Limitation

Notification hooks provide only the tool type without identifying the specific instance:

{
  "hook_name": "Notification",
  "message": "Claude needs your permission to use bash",
  "session_id": "abc123"
  // Missing: tool_use_id, tool_name, tool_input, user_options
}

Critical issue: When multiple tools of the same type are scheduled (e.g., three different Bash commands), there's no way to determine which specific tool triggered the notification.

The Out-of-Order Execution Problem

Claude Code schedules multiple tools but executes them in non-deterministic order:

Scheduled: [Bash("git log file1"), Bash("git log file2"), Read("config.json")]
Notification: "Claude needs permission to use Bash"

Question: Which Bash command is this notification about?
Answer: Unknown - no correlation information provided

External systems are forced to make FIFO assumptions, resulting in:

  • Wrong context shown to users (showing details for Tool A when Tool B is executing)
  • Approval mismatches (user approves Tool A but Tool B runs instead)
  • Inability to build reliable external notification systems

In our testing: FIFO assumptions result in 20-30% mismatch rate when multiple tools are scheduled.

Additional Problem: User Options

External systems need to present the exact options Claude Code shows (Allow, Deny, Always allow, etc.), but:

  • User options are NOT in notification hook payloads
  • User options are NOT in transcripts
  • External systems must scrape terminal output (fragile, unreliable)

Workaround Complexity

To work around these limitations, we've implemented a 732-line state machine that:

  • Intercepts PreToolUse hooks to build a queue of scheduled tools
  • Makes FIFO assumptions when notification hooks fire
  • Detects mismatches when the wrong tool executes
  • Implements self-healing to reschedule mismatched tools
  • Uses file locking to prevent race conditions

Results:

  • Success rate: 70-80% (correct tool context shown)
  • Mismatch rate: 20-30% (wrong tool shown, requires recovery)
  • Additional latency: ~50-100ms per notification for state machine processing

This complexity exists solely to infer correlation information that could be provided directly in the notification hook.

Proposed Solution

Enrich notification hook payloads with complete context including tool metadata and user options.

Enhanced Payload

{
  "hook_name": "Notification",
  "tool_use_id": "toolu_abc123xyz",
  "tool_name": "Bash",
  "tool_input": {
    "command": "git log src/main.rs",
    "description": "Show git log for file"
  },
  "user_options": [
    {
      "id": "allow",
      "label": "Allow"
    },
    {
      "id": "deny",
      "label": "Do not allow"
    },
    {
      "id": "always_allow",
      "label": "Always allow Bash for this session"
    }
  ],
  "message": "Claude needs your permission to use bash",
  "session_id": "...",
  "cwd": "..."
}

Why Each Component Is Critical

Tool Context (tool_use_id + tool_name + tool_input):

  • Provides definitive identification of which tool triggered the notification
  • Works for BOTH main agent AND sub-agent tools (100% coverage)
  • No transcript parsing required (self-contained, lower latency)
  • No FIFO guessing (eliminates 20-30% mismatch rate)

User Options (user_options):

  • External systems can render identical UI to Claude Code
  • No terminal scraping required
  • Consistent user experience across platforms (web, mobile, integrations)

How External Systems Use This

With complete context, external systems (web dashboards, mobile apps, Slack) can render accurate notifications:

Tool: Bash
Command: git log src/main.rs

[ Allow ] [ Do not allow ] [ Always allow Bash for this session ]

Result: 100% accurate notifications with consistent UX across all platforms.

Why This Solves Sub-Agent Problems

Critical Discovery: When Claude Code launches sub-agents (Explore, Plan), those sub-agents schedule their own tools. However, sub-agent tool uses don't appear in the main session transcript at ~/.claude/sessions/{session_id}/transcript.jsonl.

Impact: Correlation ID lookups via transcript fail for sub-agent tools, which represent 40-60% of tool executions in typical workflows.

Why full payload solves this: The notification hook contains all necessary context directly, regardless of whether the tool was scheduled by:

  • Main agent (human-facing)
  • Explore sub-agent (codebase exploration)
  • Plan sub-agent (planning mode)
  • Any future sub-agent types

No transcript dependency means 100% coverage for all agent types.

Alternative Solutions

Alternative 1: Correlation ID Only (Partial Solution)

Add only tool_use_id to notification hooks. External systems look up tool details from transcript.

Payload:

{
  "hook_name": "Notification",
  "tool_use_id": "toolu_abc123xyz",
  "message": "Claude needs your permission to use bash",
  ...
}

How it would work:

  1. External system receives tool_use_id in notification hook
  2. Looks up tool details from transcript JSONL
  3. Matches tool_use_id to find tool_name and tool_input

Critical Limitations:

  • Fails for sub-agent tools (not in main transcript) → only 40-60% coverage
  • Doesn't provide user options → still requires terminal scraping
  • Adds latency (transcript file I/O on every notification)
  • Race conditions (transcript may not be written yet)

Verdict: Partial solution only viable if sub-agent tool uses are added to main transcript (architectural change).

Priority

Critical - Blocking my work

Feature Category

Developer tools/SDK

Use Case Example

Scenario: Multi-Instance Claude Code Management Platform

Our Architecture:

React Dashboard → Spring Boot Service → Claude Terminal Agent → Claude Code Sessions
                  (Notification Hub)    (Daemon Process)      (tmux sessions)

We're developing a centralized web service to manage multiple Claude Code instances running across different projects, environments, and virtual machines.

Step-by-Step Workflow:

  1. Setup: User manages 5 Claude Code sessions across different VMs via web dashboard
  1. Agent Action: In one session, user asks to explore codebase. Claude schedules three operations:

``
Bash: git log src/main.rs
Bash: git diff HEAD~1
Read: ~/.claude-agent/config.json
``

  1. Notification Hook Fires:

``json
{"message": "Claude needs permission to use bash"}
``

  1. Our System Intercepts: Daemon forwards notification to Spring Boot service
  1. The Problem:
  • Which Bash command is this about? → We have to guess using FIFO
  • What options should we show? → We have to scrape terminal output
  1. Current Result:

```
Tool: Bash
Command: git log src/main.rs ← 70-80% chance this is correct

Options: [ Allow ] [ Deny ] [ Always allow ] ← Scraped from terminal

Outcome: 20-30% of the time, wrong context shown to user
```

With This Feature:

  1. Notification hook includes complete context:

``json
{
"tool_name": "Bash",
"tool_input": {"command": "git log src/main.rs"},
"user_options": [
{"id": "allow", "label": "Allow"},
{"id": "deny", "label": "Do not allow"},
{"id": "always_allow", "label": "Always allow Bash for this session"}
]
}
``

  1. Dashboard renders exact notification:

```
Tool: Bash
Command: git log src/main.rs

[ Allow ] [ Do not allow ] [ Always allow Bash for this session ]
```

  1. Result: 100% accurate, consistent UX, no guessing, no terminal scraping

Real-World Impact:

This would save us ~50-100ms per notification and eliminate our 732-line workaround, while enabling:

  • Management of Claude Code instances across 10+ projects in dev/staging/prod
  • Approval workflows for teams (security, compliance requirements)
  • Mobile notifications with full context
  • Precise audit logging for security monitoring
  • Analytics dashboards tracking tool usage patterns

Additional Context

Why Sub-Agent Tools Are a Critical Limitation

When users ask exploratory questions like "Search for API endpoints in the codebase", Claude launches Explore sub-agents that schedule multiple tools (Grep, Glob, Read). These tools trigger notification hooks, but their tool_use entries don't appear in the main session transcript.

Example:

User: "Search for error handling patterns in the codebase"

Main agent: Launches Explore sub-agent
Explore sub-agent: Schedules Grep, Glob, Read tools (5-10 tools)
Notifications fire: 5-10 notification hooks with tool_use_ids
External system: Looks up tool_use_ids in transcript
Result: ❌ 0 matches found (all sub-agent tools)
Fallback: Forced to use FIFO guessing (back to square one!)

Impact: In typical workflows where users ask exploratory questions, 40-60% of tool executions involve sub-agents. Correlation ID-only solution would fail for most real-world usage.

Broader Community Impact

While our use case involves a three-tier architecture, this limitation affects anyone building external systems that intercept hooks:

Affected Use Cases:

  • Custom notification systems (Slack, email, mobile push notifications with tool details)
  • Audit logging systems requiring precise tool execution tracking
  • Policy enforcement engines that make approval decisions based on specific tool inputs
  • Analytics dashboards tracking tool usage patterns across sessions
  • Integration platforms connecting Claude Code to external workflows
  • Security monitoring requiring exact command/file tracking

General Principle: Any system that processes notification hooks for external consumption needs reliable correlation between notification events and specific tool executions, plus the ability to render consistent user interfaces.

Technical Note on User Options

The proposed user_options structure mirrors how permission prompts work internally in Claude Code. External systems need to replicate the exact choices Claude Code presents to maintain UX consistency across platforms (web dashboards, mobile apps, Slack integrations).

Currently, these options are:

  • Rendered in the terminal but not exposed in hook payloads
  • Not available in transcripts
  • Vary by tool type and context (e.g., "Always allow Bash for this session" vs. one-time approval)

External systems have no reliable way to determine what options should be presented.

---

We're happy to provide additional context, test beta implementations, or collaborate on implementation details if helpful.

Thank you for considering this enhancement! We believe it would significantly improve the Claude Code ecosystem for anyone building external integrations.

View original on GitHub ↗

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