Fix onPreToolUse and permission hooks not firing for sub-agent tool calls#89
Draft
Fix onPreToolUse and permission hooks not firing for sub-agent tool calls#89
onPreToolUse and permission hooks not firing for sub-agent tool calls#89Conversation
1 task
Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/4ea5c73c-1940-4862-8afb-019a8de0bca2 Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/4ea5c73c-1940-4862-8afb-019a8de0bca2 Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix onPreToolUse hook to fire for sub-agent tool calls
Fix Apr 21, 2026
onPreToolUse and permission hooks not firing for sub-agent tool calls
Collaborator
|
I will mark this Ready for review to get Copilot's review on it, but then take it back to draft. |
Contributor
There was a problem hiding this comment.
Copilot's findings
Comments suppressed due to low confidence (3)
src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java:421
- The fallback selection is explicitly non-deterministic when multiple sessions have permission handlers. For permission decisions this can produce inconsistent (and potentially unsafe) behavior if different sessions have different policies, since an “unknown” session could be approved/denied depending on map iteration order. Consider making the fallback deterministic or safer when ambiguous (e.g., only fall back when exactly one session has a handler; otherwise return denied and/or log a warning).
/**
* Finds the first registered session that has a permission handler registered.
* <p>
* Used as a fallback when the session ID in an incoming
* {@code permission.request} request is not in the registry (e.g. sub-agent
* sessions created internally by the CLI). When multiple sessions have
* permission handlers registered, the selection is non-deterministic because
* the underlying {@link java.util.concurrent.ConcurrentHashMap} does not
* guarantee iteration order.
*
* @return a session with a permission handler, or {@code null} if none is found
*/
private CopilotSession findSessionWithPermissionHandler() {
for (CopilotSession s : sessions.values()) {
if (s.hasPermissionHandler()) {
return s;
}
}
return null;
src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java:401
- Similarly, hooks fallback is non-deterministic across multiple sessions with hooks registered, which can lead to surprising cross-session behavior (e.g., hooks from an unrelated session gating tool execution for a sub-agent). Consider applying the same “only if unambiguous” rule (exactly one session with hooks) or otherwise choosing a deterministic session selection strategy and logging when falling back.
/**
* Finds the first registered session that has a hooks handler registered.
* <p>
* Used as a fallback when the session ID in an incoming {@code hooks.invoke}
* request is not in the registry (e.g. sub-agent sessions created internally by
* the CLI). When multiple sessions have hooks registered, the selection is
* non-deterministic because the underlying
* {@link java.util.concurrent.ConcurrentHashMap} does not guarantee iteration
* order.
*
* @return a session with hooks, or {@code null} if none is found
*/
private CopilotSession findSessionWithHooks() {
for (CopilotSession s : sessions.values()) {
if (s.hasHooksHandler()) {
return s;
}
}
return null;
}
src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java:305
- Same as permission fallback: when an unknown/sub-agent sessionId is handled by a different (fallback) CopilotSession, the HookInvocation created inside CopilotSession.handleHooksInvoke() will report the fallback session’s sessionId rather than the originating request sessionId. If callers use invocation.getSessionId() for correlation or policy, this will be misleading. Consider allowing the dispatcher to pass the originating sessionId through to the HookInvocation (e.g., via an overload or parameter).
CopilotSession session = sessions.get(sessionId);
if (session == null) {
// Fall back to any registered session that has hooks registered.
// This handles sub-agent sessions created internally by the CLI whose IDs
// are not in the SDK's session registry.
session = findSessionWithHooks();
}
- Files reviewed: 3/3 changed files
- Comments generated: 1
Comment on lines
+194
to
199
| // Fall back to any registered session that has a permission handler. | ||
| // This handles sub-agent sessions created internally by the CLI whose IDs | ||
| // are not in the SDK's session registry. | ||
| session = findSessionWithPermissionHandler(); | ||
| } | ||
| if (session == null) { |
1 task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #64.
hooks.invokeandpermission.requestRPC calls from sub-agent sessions were silently dropped because the CLI creates sub-agent sessions internally with their own session IDs that are never registered in the SDK's session map — causing strict ID lookups to miss them entirely.Before the change?
handleHooksInvokereturned a-32602error for any session ID not in the SDK registry. Sub-agent hook invocations were silently lost.handlePermissionRequestreturneddeniedfor unknown session IDs. Sub-agent permission checks fell through with no user-visible handler.onPreToolUse/onPostToolUsehooks registered on the parent session were invisible to tools invoked by the CLI-managed sub-agent.After the change?
RpcHandlerDispatcher: When ahooks.invokeorpermission.requestarrives with an unrecognized session ID, the dispatcher falls back to the first registered session that has the appropriate handler:nulloutput (no-op) if no session has hooks — no error sent.deniedif no session has a handler — same safe default, no-32602.CopilotSession: Two new package-private query methods support the fallback lookup:hasHooksHandler()— true if hooks with at least one handler are registered.hasPermissionHandler()— true if a permission handler is registered.RpcHandlerDispatcher: Two new private helpers,findSessionWithHooks()andfindSessionWithPermissionHandler(), iterate registered sessions to find a suitable fallback. Selection is non-deterministic when multiple sessions have handlers (documented in Javadoc).RpcHandlerDispatcherTest: Updated existinghooksInvokeWithUnknownSessiontest (renamed tohooksInvokeWithUnknownSessionAndNoFallback) and added four new unit tests covering both the fallback-succeeds and no-fallback-available scenarios for hooks and permissions.Pull request checklist
mvn spotless:applyhas been run to format the codemvn clean verifypasses locallyDoes this introduce a breaking change?