Agent Context Detention in Hook Events

Status Fixed / completed
Maintainer reply None cached
Activity 11 comments · opened Aug 30, 2025 · closed Aug 17, 2026

Environment

  • Platform (select one):
  • [ ] Anthropic API
  • [x] AWS Bedrock
  • [ ] Google Vertex AI
  • [ ] Other:
  • Claude CLI version: 1.0.96
  • Operating System: macOS
  • Terminal: Ghostty

Feature Description

Add agent context information to hook events so that hooks can determine whether they're running within an agent workflow (e.g., when Claude is using the Task tool with subagents).

Use Case / Problem Statement

Currently, hooks receive rich context about tool usage, session information, and transcript data, but cannot determine if they're executing within an agent context. This limits the ability to:

  1. Apply different security policies for agent vs. direct user interactions
  2. Implement agent-specific logging or auditing
  3. Adjust hook behavior based on execution context
  4. Provide better debugging information for agent workflows

Example use case (git-sub agent)

It would be ideal for me to develop a hook that checks whether Bash(git:*) calls were being performed inside of a slash command kicked off to a sub-agent. To know what sub-agent(s) are being used to run it would let me determine if it's doing something outside of my instructions (such as saying "never commit to the repo without using @agent-git-expert").

Proposed Solution

Add agent context information to hook events, such as:

  • IsAgentContext bool - indicates if running within an agent
  • AgentName string - type of agent (e.g., "general-purpose", "code-reviewer")
  • ParentSessionID string - session ID of the parent context
  • AgentDepth int - nesting level for multi-agent scenarios

This could be added to existing event types (PreToolUseEvent, PostToolUseEvent, etc.) or provided through the hook context.

Expected Behavior

Using cchooks, it would work like this:

func (h *MyHook) preToolUseHandler(ctx context.Context, event *cchooks.PreToolUseEvent) {
    if event.IsAgentContext {
        // Apply agent-specific logic
        log.Printf("Agent %s executing %s", event.AgentType, event.ToolName)
    }
}

Alternative Solutions

If direct agent context isn't feasible, consider:

  1. Adding metadata fields to events that agents can populate
  2. Providing session hierarchy information
  3. Adding agent-related information to the existing SessionID structure

Additional Context

This feature would enhance the security and observability capabilities of Claude Code hooks, particularly for users building sophisticated agent workflows that require different governance policies than direct user interactions.

View original on GitHub ↗

10 Comments

coygeek · 1 year ago

Hey,

This is a great feature request. I've been thinking about a similar problem for managing how my team uses claude code, and being able to apply different policies for sub-agents vs. the main agent loop would be a huge security and governance win. Your proposed solution of adding AgentName, IsAgentContext, etc., directly to the hook payload is definitely the ideal, clean solution. +1 from me on that.

In the meantime, I was digging through the docs and found a potential workaround that might get you most of the way there. It's not as clean as your proposal, but it seems workable.

Workaround: Inspecting the Session Transcript

The hook event payload includes the transcript_path. We can use this to inspect the conversation history and figure out if we're inside a sub-agent task.

When Claude decides to use a sub-agent, it calls the Task tool. The input to that tool call contains the name of the sub-agent being invoked. Any subsequent tool calls made by that sub-agent will appear in the transcript after that Task tool call.

So, a PreToolUse hook for Bash(git:*) could look something like this:

  1. Receive the hook payload with the transcript_path.
  2. Read the last few lines of the transcript file (it's a JSONL file).
  3. Work backward from the end of the transcript to find the most recent tool_use message where the tool name was Task.
  4. Check the input of that Task tool call to see which sub-agent was invoked.
  5. If it's not @agent-git-expert (or whatever you've named your agent), the hook can block the git command by exiting with code 2 and sending a message back to Claude.

Here’s a rough sketch of what a Python script for this hook might look like.

.claude/hooks/validate-git-agent.py

#!/usr/bin/env python3
import json
import sys
import os

# The only agent allowed to run git commands
ALLOWED_GIT_AGENT = "agent-git-expert"

def find_parent_agent(transcript_path: str) -> str | None:
    """
    Reads the transcript and finds the name of the most recently invoked sub-agent.
    """
    try:
        with open(transcript_path, 'r') as f:
            lines = f.readlines()

        # Look at the last N messages to find the parent Task tool_use
        # This is an approximation but should be good enough for most cases.
        for line in reversed(lines):
            try:
                message = json.loads(line)
                if (message.get("type") == "assistant" and 
                    isinstance(message.get("message", {}).get("content"), list)):
                    
                    for content_item in message["message"]["content"]:
                        if content_item.get("type") == "tool_use" and content_item.get("name") == "Task":
                            # The agent name is in the 'description' field of the task input.
                            # It often looks like: "Use the @agent-git-expert agent to..."
                            task_description = content_item.get("input", {}).get("description", "")
                            if f"@{ALLOWED_GIT_AGENT}" in task_description:
                                return ALLOWED_GIT_AGENT
                            # Could add more sophisticated parsing here to extract any agent name
                            return "some-other-agent" # Found a different agent
            except (json.JSONDecodeError, KeyError):
                continue
    except Exception:
        # If we can't parse the transcript, fail safe and assume no agent.
        return None
    
    return None # No agent context found

def main():
    try:
        event_data = json.load(sys.stdin)
        tool_name = event_data.get("tool_name")
        transcript_path = event_data.get("transcript_path")

        # We only care about Bash commands for git
        if tool_name != "Bash" or not event_data.get("tool_input", {}).get("command", "").startswith("git"):
            sys.exit(0)
            
        parent_agent = find_parent_agent(transcript_path)

        if parent_agent == ALLOWED_GIT_AGENT:
            # Correct agent is being used, allow it.
            sys.exit(0)
        else:
            # Wrong (or no) agent is trying to use git. Block it.
            error_message = f"SECURITY POLICY VIOLATION: Git commands must be executed by the '{ALLOWED_GIT_AGENT}' sub-agent. You used '{parent_agent or 'the main agent'}'. Please delegate this task to the correct agent."
            print(error_message, file=sys.stderr)
            # Exit code 2 blocks the tool and feeds stderr back to Claude
            sys.exit(2)

    except Exception as e:
        print(f"Error in git validation hook: {e}", file=sys.stderr)
        # Non-zero, non-2 exit code indicates a non-blocking error to the user
        sys.exit(1)

if __name__ == "__main__":
    main()

Then in your .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/validate-git-agent.py"
          }
        ]
      }
    ]
  }
}

Limitations of this workaround:

  • It's definitely more complex than having a simple boolean flag in the event payload.
  • It relies on parsing the transcript, which could be slow on very long conversations.
  • The logic to extract the agent name from the Task input's description string is a bit fragile and depends on Claude's phrasing.

Still, it seems like a viable path until we get first-class support for agent context in hooks. Your proposal is much better for the long run.

Hope this helps

ias-z · 11 months ago

👍 This would be incredibly useful for my use case as well!

My Use Case: Content Isolation with File Access Controls

I'm implementing content isolation where the main agent should be restricted from reading certain files (to prevent context pollution), while subagents need access to these files. Currently, I have a PreToolUse hook that needs to distinguish between agent contexts:

Current hook structure

"PreToolUse": [
  {
    "matcher": "Read",
    "hooks": [
      {
        "type": "command",
        "command": "./hooks/validate-file-access.sh",
        "description": "Enforce content isolation - prevent main agent access to specific files"
      }
    ]
  }
]

Current Workaround

I'm having to implement a hacky solution:

Track subagent lifecycle by detecting Task tool invocations
Use file-based flags to maintain agent state
Clean up with PostToolUse/SubagentStop hooks
Handle parallel subagents with counters

This is error-prone and doesn't scale well.

What I Need

For shell script hooks, having environment variables like:

CLAUDE_AGENT_NAME - to identify the current agent

github-actions[bot] · 8 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

Hearmeman24 · 7 months ago

This is a great feature request and would love to have it

ArrichM · 7 months ago

+1 on this

Butanium · 6 months ago

More comprehensive proposal for this: #16424

In the meantime, there's a workaround using tool_use_id + transcript grepping to detect subagent context: https://github.com/anthropics/claude-code/issues/16424#issuecomment-3880522301

🤖 Generated with Claude Code

abhibarkade · 5 months ago

I've submitted a pull request (#36279) that addresses this. It adds documentation, test utilities, and concrete examples for the four new agent-context fields (is_subagent, agent_name, parent_session_id, agent_depth).

This should make it much easier to implement targeted security policies and cleaner denial messages for subagents. Feel free to take a look!

sfriedenberg-etsy · 5 months ago

This is still a relevant issue, and the feature would be very welcome.

Nxt3 · 5 months ago

Pretty sure this has been added for a few releases now. agent_id is populated if you're in the context of a subagent.

abhibarkade · 5 months ago

Hi @Nxt3, thanks for the heads up! I completely missed that agent context fields (agent_id and agent_type) were natively added to the hook payload in v2.1.69.

I've gone ahead and completely refactored this PR. Instead of trying to introduce new fields, this PR now strictly serves to document and provide examples for the native agent_id and agent_type fields.

I've just pushed updates that:

  • Fix SKILL.md, advanced.md, and patterns.md to properly document the official schema, explaining how to use agent_id to branch for subagent contexts.
  • Update the test-hook.sh developer utility to properly inject agent_id and agent_type when generating --subagent sample payloads.
  • Update the Bash and Python hook examples (validate-git-agent.py, agent-aware-bash-validator.sh, etc.) to demonstrate extracting and utilizing these specific native fields.

Since the core engine functionality is already there, I hope this documentation-and-examples PR is a helpful addition to the repository! Let me know if any further tweaks are needed.

Showing cached comments. Read the full discussion on GitHub ↗