Skip to content

Fix sub-agent hook propagation: expose sessionId on hook inputs#1290

Open
SteveSandersonMS wants to merge 10 commits into
mainfrom
stevesa/subagent-permission-hooks
Open

Fix sub-agent hook propagation: expose sessionId on hook inputs#1290
SteveSandersonMS wants to merge 10 commits into
mainfrom
stevesa/subagent-permission-hooks

Conversation

@SteveSandersonMS
Copy link
Copy Markdown
Contributor

@SteveSandersonMS SteveSandersonMS commented May 14, 2026

Summary

Fixes #1097 -- preToolUse/postToolUse hooks were not being invoked for tool calls made by sub-agents spawned via the task tool.

Changes

Hook input types (all SDKs)

  • Added sessionId to all 6 hook input types (PreToolUseHookInput, PostToolUseHookInput, UserPromptSubmittedHookInput, SessionStartHookInput, SessionEndHookInput, ErrorOccurredHookInput) across Node.js, Python, Go, .NET, and Rust.
  • This field carries the runtime session ID of the session that triggered the hook, allowing consumers to distinguish parent session hooks from sub-agent hooks by comparing input.sessionId with invocation.sessionId.

E2E tests (all SDKs)

  • Added subagent_hooks E2E tests for all five SDKs, sharing a single snapshot.
  • Each test registers preToolUse/postToolUse hooks, sends a prompt that spawns a sub-agent (task tool -> view tool), and asserts:
    • Hooks fire for the parent's task tool call
    • Hooks fire for the sub-agent's view tool call
    • sessionId differs between parent and sub-agent hook invocations

Requirements

  • Requires a corresponding runtime fix that propagates ad-hoc hooks to child sessions.
  • Requires the COPILOT_EXP_COPILOT_CLI_SESSION_BASED_SUBAGENTS=true feature flag (the legacy callback-bridge path does not support SDK tool hooks for sub-agents).

Note: E2E tests for sub-agent hooks will fail until the runtime is updated with the corresponding fix.

SteveSandersonMS and others added 9 commits May 14, 2026 12:23
Adds an E2E test that verifies preToolUse and postToolUse hooks fire
for tool calls made by sub-agents spawned via the task tool. The test
spawns an explore agent that reads a file using the view tool and
asserts that hooks are invoked for the sub-agent's view tool call.

This test requires the runtime fix (getEffectiveHooks() propagation)
from github/copilot-agent-runtime and the SESSION_BASED_SUBAGENTS
feature flag to be enabled.

Relates to #1097

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The runtime fix (getEffectiveHooks) only applies to the session-based
subagent path. The legacy callback-bridge path does not support SDK
preToolUse/postToolUse hooks for sub-agent tool calls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Track hook invocations with sequential indices to assert that:
- 'task' hooks fire for the parent agent
- 'view' hooks fire for the sub-agent
- Sub-agent 'view' hooks fire after the parent's 'task' pre-hook,
  proving they come from the spawned sub-agent

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The runtime sends sessionId in hook inputs identifying which session
(parent or sub-agent) invoked the tool. Map this to agentSessionId on
BaseHookInput so SDK consumers can distinguish parent tool calls from
sub-agent tool calls.

Update the E2E test to assert that parent 'task' hooks and sub-agent
'view' hooks carry different agentSessionIds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace agentSessionId with sessionId on BaseHookInput — this is the
runtime session ID passed through from the wire, which differs between
parent and sub-agent tool calls. Simplify the E2E test to focus on
verifying hooks fire for both parent and sub-agent tools and that
their sessionIds differ.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…thon, Go, .NET

- Add sessionId field to BaseHookInput/PreToolUseHookInput/PostToolUseHookInput
  in Python, Go, and .NET SDKs (already present in Node.js)
- Create subagent_hooks E2E tests for all three languages sharing the same
  snapshot as the Node.js test
- Tests verify hooks fire for both parent and sub-agent tool calls with
  distinct sessionIds

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add session_id field to all 6 hook input structs (PreToolUseInput,
  PostToolUseInput, UserPromptSubmittedInput, SessionStartInput,
  SessionEndInput, ErrorOccurredInput)
- Update existing unit test JSON fixtures with sessionId
- Create subagent_hooks E2E test sharing the same snapshot as other SDKs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
….NET

- Python: Add sessionId to UserPromptSubmittedHookInput, SessionStartHookInput,
  SessionEndHookInput, ErrorOccurredHookInput. Remove unused BaseHookInput.
- .NET: Add SessionId to UserPromptSubmittedHookInput, SessionStartHookInput,
  SessionEndHookInput, ErrorOccurredHookInput.

All 6 hook input types now have sessionId in every SDK (Node, Python, Go, .NET, Rust).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS requested a review from a team as a code owner May 14, 2026 12:16
Copilot AI review requested due to automatic review settings May 14, 2026 12:16
Comment thread dotnet/test/E2E/SubagentHooksE2ETests.cs Dismissed
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1290 · ● 1.9M

Comment thread python/e2e/test_subagent_hooks_e2e.py Outdated
Instead of polluting global os.environ at module import time, create a
dedicated client with the feature flag in its env dict. This matches how
the .NET and Go tests scope the env var to the client instance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review ✅

This PR maintains excellent cross-SDK consistency. The sessionId field has been added to all 6 hook input types across all 5 SDK implementations:

Field name SDK
sessionId Node.js (BaseHookInput — inherited by all hook input interfaces)
sessionId Python (added directly to each TypedDict subclass)
SessionID Go (with json:"sessionId" tag — follows Go's acronym convention)
SessionId .NET (with [JsonPropertyName("sessionId")] — follows PascalCase convention)
session_id Rust (with #[serde(rename_all = "camelCase")] — follows snake_case convention)

All 5 SDKs also include the new subagent_hooks E2E test sharing a single snapshot, and the test assertions are semantically equivalent across languages.

The removal of Python's BaseHookInput class is a non-breaking internal change since it was never part of the public API surface (__init__.py exports).

No consistency issues found.

Generated by SDK Consistency Review Agent for issue #1290 · ● 723.6K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SessionHooks (onPreToolUse / onPostToolUse) are not invoked for tool calls made by sub-agents spawned via task

1 participant