Skip to content

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

Merged
SteveSandersonMS merged 14 commits into
mainfrom
stevesa/subagent-permission-hooks
May 18, 2026
Merged

Fix sub-agent hook propagation: expose sessionId on hook inputs#1290
SteveSandersonMS merged 14 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 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
@github-actions

This comment has been minimized.

SteveSandersonMS and others added 10 commits May 18, 2026 09:40
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>
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>
@SteveSandersonMS SteveSandersonMS force-pushed the stevesa/subagent-permission-hooks branch from e10f695 to 7021a8e Compare May 18, 2026 08:40
@github-actions

This comment has been minimized.

The bundled runtime changed 'retrieve the full results' to 'retrieve
unread results' in agent completion notifications. Also fix ruff
formatting on the Python E2E test.

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

This comment has been minimized.

SteveSandersonMS and others added 2 commits May 18, 2026 10:20
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

The session_test hooks tests were missing the sessionId field in their
input JSON, causing deserialization failures after adding the required
session_id field to hook input structs.

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. Here's what was verified:

sessionId field added to all 6 hook input types across all 5 SDKs

Hook Input Type Node.js Python Go .NET Rust
PreToolUseHookInput
PostToolUseHookInput
UserPromptSubmittedHookInput
SessionStartHookInput
SessionEndHookInput
ErrorOccurredHookInput

Naming follows language conventions correctly

  • Node.js/TypeScript: sessionId (camelCase on BaseHookInput) ✅
  • Python: sessionId (TypedDict key, matching JSON wire format) ✅
  • Go: SessionID with json:"sessionId" tag (idiomatic Go: ID not Id) ✅
  • .NET: SessionId with [JsonPropertyName("sessionId")] (idiomatic C# PascalCase) ✅
  • Rust: session_id with #[serde(rename_all = "camelCase")] (idiomatic snake_case + serde) ✅

E2E tests added for all 5 SDKs, sharing a single snapshot ✅

No consistency issues found. The PR is a model example of a cross-SDK feature addition.

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

@SteveSandersonMS SteveSandersonMS merged commit f6c1adf into main May 18, 2026
43 checks passed
@SteveSandersonMS SteveSandersonMS deleted the stevesa/subagent-permission-hooks branch May 18, 2026 09:56
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