Skip to content

Fix hooks and permission dispatch for sub-agent sessions#68

Closed
Copilot wants to merge 32 commits intomainfrom
copilot/fix-onpretooluse-for-subagents
Closed

Fix hooks and permission dispatch for sub-agent sessions#68
Copilot wants to merge 32 commits intomainfrom
copilot/fix-onpretooluse-for-subagents

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 11, 2026

Resolves #64


Before the change?

  • hooks.invoke and permission.request RPC handlers look up sessions strictly by ID. Sub-agent sessions are created internally by the CLI with their own session IDs that are never registered in the SDK's session map. This causes handleHooksInvoke to return a -32602 error and handlePermissionRequest to return denied, silently swallowing the hook/permission call for any tool the sub-agent invokes.

After the change?

  • When the CLI sends hooks.invoke or permission.request with a session ID that is not in the SDK's session registry (e.g. for CLI-created sub-agent sessions), the dispatcher now falls back to a registered session that has the appropriate handler:

    • handleHooksInvoke: Falls back to a session with hooks registered. If no session has hooks, returns null output (no-op) instead of a -32602 error.
    • handlePermissionRequest: Falls back to a session with a permission handler. If no session has a handler, returns denied as before.
  • Added hasHooksHandler() and hasPermissionHandler() package-private query methods to CopilotSession to support the fallback lookup.

  • Added findSessionWithHooks() and findSessionWithPermissionHandler() helper methods to RpcHandlerDispatcher that iterate over registered sessions to find a suitable fallback.

  • Added unit tests for both fallback scenarios (sub-agent session ID routes to parent session's handler) and no-fallback scenarios (no registered session has the handler).

Pull request checklist

  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)
  • mvn spotless:apply has been run to format the code
  • mvn clean verify passes locally

Does this introduce a breaking change?

  • Yes
  • No

Copilot AI and others added 2 commits April 11, 2026 23:29
When the CLI creates sub-agent sessions internally, their session IDs
are not in the SDK's session registry. The hooks.invoke and
permission.request handlers now fall back to any registered session
that has the appropriate handler, enabling hooks and permission checks
to fire for sub-agent tool calls.

Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/868c6fed-c57d-4d9f-806c-eca509096672

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix onPreToolUse hook for sub-agent tool calls Fix onPreToolUse hook not firing for sub-agent tool calls Apr 11, 2026
Copilot AI requested a review from brunoborges April 11, 2026 23:32
@brunoborges brunoborges marked this pull request as ready for review April 12, 2026 00:22
Copilot AI review requested due to automatic review settings April 12, 2026 00:22
@brunoborges
Copy link
Copy Markdown
Collaborator

@copilot shouldn't subagents ask for the parent session for hooks and permissions? How is .NET SDK doing this?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes SDK-side dispatching of hooks.invoke and permission.request for CLI-created sub-agent sessions whose session IDs are not present in the SDK session registry, ensuring tool hooks and permission handlers can still run.

Changes:

  • Add internal CopilotSession query methods to detect whether hooks / permission handlers are registered.
  • Update RpcHandlerDispatcher to fall back to a registered session with hooks/permission handlers when the incoming sessionId is unknown (and make hooks.invoke a no-op with output: null if none exist).
  • Add/adjust unit tests to cover fallback and no-fallback behavior for both hooks and permission requests.
Show a summary per file
File Description
src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java Adds fallback session selection for unknown sessionId in permission.request and hooks.invoke, plus helper finders.
src/main/java/com/github/copilot/sdk/CopilotSession.java Adds package-private hasHooks() / hasPermissionHandler() helpers used by dispatcher fallback.
src/test/java/com/github/copilot/sdk/RpcHandlerDispatcherTest.java Adds tests for fallback/no-op behavior for sub-agent permission and hook invocations.

Copilot's findings

Comments suppressed due to low confidence (3)

src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java:205

  • When falling back for an unknown sessionId, the permission handler is invoked via the chosen fallback CopilotSession, but the PermissionInvocation inside CopilotSession uses that session’s own sessionId. This means sub-agent permission requests will present the parent (fallback) sessionId to the handler, losing the actual request sessionId from the RPC params. Consider plumbing the original sessionId through (e.g., pass it into handlePermissionRequest / PermissionInvocation) so handlers can correctly attribute sub-agent requests.

This issue also appears on line 296 of the same file.

                String sessionId = params.get("sessionId").asText();
                JsonNode permissionRequest = params.get("permissionRequest");

                CopilotSession session = sessions.get(sessionId);
                if (session == null) {
                    // The CLI may send permission requests for sub-agent sessions
                    // whose IDs are not tracked by the SDK. Fall back to any
                    // registered session that has a permission handler.
                    session = findSessionWithPermissionHandler();
                }
                if (session == null) {
                    var result = new PermissionRequestResult()
                            .setKind(PermissionRequestResultKind.DENIED_COULD_NOT_REQUEST_FROM_USER);
                    rpc.sendResponse(Long.parseLong(requestId), Map.of("result", result));
                    return;
                }

src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java:312

  • When falling back for an unknown sessionId, the HookInvocation created in CopilotSession uses the fallback session’s sessionId, not the RPC param sessionId. For sub-agent hooks.invoke calls this can misattribute which session triggered onPreToolUse/onPostToolUse. Consider passing the original sessionId into hook handling so HookInvocation reflects the request’s sessionId even when a different session instance provides the handlers.
                String sessionId = params.get("sessionId").asText();
                String hookType = params.get("hookType").asText();
                JsonNode input = params.get("input");

                CopilotSession session = sessions.get(sessionId);
                if (session == null) {
                    // The CLI may send hooks.invoke for sub-agent sessions whose IDs
                    // are not tracked by the SDK. Fall back to any registered session
                    // that has hooks so that application-level hooks (e.g. onPreToolUse)
                    // also fire for sub-agent tool calls.
                    session = findSessionWithHooks();
                }
                if (session == null) {
                    rpc.sendResponse(Long.parseLong(requestId), Collections.singletonMap("output", null));
                    return;
                }

src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java:375

  • findSessionWithPermissionHandler() has the same non-deterministic “first match” issue as findSessionWithHooks(): if more than one session has a permission handler, ConcurrentHashMap iteration order makes the fallback session unpredictable, which can change allow/deny decisions unexpectedly. Consider requiring a single unambiguous fallback candidate or adding a deterministic selection mechanism.
     * If multiple sessions have permission handlers, the first one found is
     * returned. In practice, SDK users typically register a single permission
     * handler that covers all sub-agent activity.
     *
     * @return a session with a permission handler, or {@code null} if none found
     */
    private CopilotSession findSessionWithPermissionHandler() {
        for (CopilotSession s : sessions.values()) {
            if (s.hasPermissionHandler()) {
                return s;
            }
        }
        return null;
    }
  • Files reviewed: 3/3 changed files
  • Comments generated: 1

Comment on lines +341 to +354
* If multiple sessions have hooks registered, the first one found is returned.
* In practice, SDK users typically register hooks on a single session that
* covers all sub-agent activity.
*
* @return a session with hooks, or {@code null} if none found
*/
private CopilotSession findSessionWithHooks() {
for (CopilotSession s : sessions.values()) {
if (s.hasHooks()) {
return s;
}
}
return null;
}
Copy link

Copilot AI Apr 12, 2026

Choose a reason for hiding this comment

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

findSessionWithHooks() returns the “first” session found with hooks, but sessions is a ConcurrentHashMap so iteration order is not deterministic. If multiple sessions register hooks, fallback selection becomes effectively random and could run the wrong hooks. Consider only falling back when exactly one candidate session has hooks (otherwise return an error / null), or introduce a deterministic selection strategy (e.g., track a designated ‘default hooks’ session).

This issue also appears on line 362 of the same file.

Copilot uses AI. Check for mistakes.
All three upstream SDKs (C#, TypeScript, Go) throw/error for unknown
session IDs in hooks.invoke and permission.request handlers with no
fallback logic. The sub-agent hooks gap exists in all SDK
implementations and should be addressed at the CLI/protocol level
rather than with SDK heuristics.

Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/50bf8907-712c-4792-aeea-219889b1ffa9

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 12, 2026

@copilot shouldn't subagents ask for the parent session for hooks and permissions? How is .NET SDK doing this?

I checked all three upstream SDKs. They all throw/error for unknown session IDs in hooks.invoke and permission.request with no fallback logic:

  • .NET: client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}")
  • Node.js: throw new Error("Session not found: " + params.sessionId)
  • Go: return nil, &jsonrpc2.Error{Code: -32602, Message: "unknown session " + req.SessionID}

None of the upstream SDKs implement sub-agent hook forwarding. The gap exists across all SDK implementations, meaning the issue should be addressed at the CLI/protocol level (the CLI should route sub-agent hooks using the parent session ID).

Reverted the SDK-level fallback in 1d0bf62 to match upstream behavior.

Copilot AI changed the title Fix onPreToolUse hook not firing for sub-agent tool calls Document upstream SDK behavior for sub-agent hook/permission dispatch Apr 12, 2026
@edburns
Copy link
Copy Markdown
Collaborator

edburns commented Apr 12, 2026

What is this issue #125 of which you speak, @brunoborges ?

@brunoborges
Copy link
Copy Markdown
Collaborator

What is this issue #125 of which you speak, @brunoborges ?

Typo. It is issue #64

@edburns
Copy link
Copy Markdown
Collaborator

edburns commented Apr 14, 2026

@copilot analyze the changes in this PR for overlap (and therefore for potential conflict) with the work being done in #70 . Respond with a report that allows a human DRI to assess the factors regarding merging this PR now or after #70 has been merged.

Note that #70 is still WIP, but you should be able to get an idea of its blast radius.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 14, 2026

@copilot analyze the changes in this PR for overlap (and therefore for potential conflict) with the work being done in #70 . Respond with a report that allows a human DRI to assess the factors regardi...

Overlap analysis: this PR (#68) vs. codegen PR (#70)

This PR's current state: Zero file changes. The SDK-level fallback for sub-agent hooks was implemented and then reverted in 1d0bf62 to match upstream .NET/Node.js/Go behavior. All three files (CopilotSession.java, RpcHandlerDispatcher.java, RpcHandlerDispatcherTest.java) are identical to main.

PR #70's blast radius (non-generated):

  • .gitattributes, .gitignore, pom.xml, config/checkstyle/checkstyle.xml, docs/WORKFLOWS.md — infrastructure/build changes
  • scripts/codegen/* — new codegen tooling
  • src/generated/java/** — ~189 new generated files (event types + RPC DTOs)

Overlap: None. PR #70 touches build config, codegen scripts, and generated source under src/generated/java/. This PR touches (or rather, no longer touches) src/main/java/com/github/copilot/sdk/RpcHandlerDispatcher.java, CopilotSession.java, and their tests. There are no shared files.

Conflict risk: Zero. The two PRs modify completely disjoint sets of files.

Merge order recommendation: Either order is safe. Since this PR currently carries no code changes (it documents the upstream behavior decision), merging it now or after #70 produces the same result with no conflict resolution needed. If the intent is to close issue #64 with the "matches upstream, defer to CLI" resolution, this PR can be merged at any time.

Copilot AI requested a review from edburns April 14, 2026 13:32
Copy link
Copy Markdown
Collaborator

@edburns edburns left a comment

Choose a reason for hiding this comment

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

@copilot This PR shows 0 files changed. Rebase to HEAD of main and then produce new commits to implement the original intent.

Copilot AI and others added 23 commits April 21, 2026 15:14
modified: .github/aw/actions-lock.json
- Adds new pinned entry for `actions/github-script@v9` (sha `373c709c...`)
- Updates `github/gh-aw-actions/setup` pin from `v0.63.1` to `v0.68.3` with new sha

modified: .github/workflows/weekly-reference-impl-sync.lock.yml
- Regenerated by `gh aw compile` (v0.68.3) from the `.md` source that was hand-edited in PR #77 without recompiling
- Compiler upgraded from v0.51.6 to v0.68.3; schema version v1 to v3
- Frontmatter hash updated to reflect current `.md` content
- `create_issue` assignee now correctly reflects `copilot-swe-agent` (was `copilot`)
- `assign_to_agent` now includes `model: claude-opus-4.6`
- Safe-outputs config updated with new tool entries (`create_report_incomplete_issue`, `report_incomplete`, `noop`)
- Action pin references updated to match newer gh-aw-actions versions

Signed-off-by: Ed Burns <edburns@microsoft.com>

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Introduces a TypeScript-based code generator (scripts/codegen/java.ts) that reads
api.schema.json and session-events.schema.json to produce:

- Typed session event classes (sealed hierarchy under AbstractSessionEvent)
- Typed RPC wrapper classes (ServerRpc, SessionRpc) for JSON-RPC methods
- Jackson-annotated records with @JsonCreator, @JsonProperty, @JsonInclude

Migrates the hand-written events package to auto-generated types, wires the
generated RPC wrappers into CopilotClient and CopilotSession, and adds
comprehensive tests including E2E coverage.

Also includes: Windows CLI path resolution fixes, shared TestUtil extraction,
lazy getRpc() initialization, race condition fix in SessionEventsE2ETest, and
a guard preventing agentic sync from modifying src/generated/java/ files.

Fix review comments: getRpc() IllegalStateException, UnknownSessionEvent wire type, anyOf heuristic, remove unused var

Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/9b8b782c-22ad-450f-885d-2b11d5808a0c

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>

Revert "Fix review comments: getRpc() IllegalStateException, UnknownSessionEvent wire type, anyOf heuristic, remove unused var"

This reverts commit ef1de83.

Fix Big Risk 1. `sendExpandedToolResult()` bypasses the typed wrapper (HIGH)

scripts/codegen/java.ts
- Remove the special-case `anyOf` rule that picked `String` when exactly 2 non-null branches included a string type. Multi-branch `anyOf` now falls through to `Object`, matching the C# reference generator's behavior.

src/generated/java/com/github/copilot/sdk/generated/rpc/SessionToolsHandlePendingToolCallParams.java
- Change the `result` field type from `String` to `Object` (regenerated output reflecting the `anyOf` rule fix in `java.ts`).

src/main/java/com/github/copilot/sdk/CopilotSession.java
- Replace the `sendExpandedToolResult(requestId, toolResult)` call with a direct typed-wrapper call: `getRpc().tools.handlePendingToolCall(new SessionToolsHandlePendingToolCallParams(...))`.
- Delete the `sendExpandedToolResult()` private method and its Javadoc block, which are no longer needed now that the `result` field accepts `Object`.

Signed-off-by: Ed Burns <edburns@microsoft.com>

On branch copilot/add-classical-code-gen-workflow-ready-for-review

modified:   scripts/codegen/java.ts

- Put `visible = true` on `SessionEventEvent`.
- Add `type` property. on `UnknownSessionEvent`.

modified:   src/generated/java/com/github/copilot/sdk/generated/SessionEvent.java
modified:   src/generated/java/com/github/copilot/sdk/generated/UnknownSessionEvent.java

- Regenerated.

modified:   src/main/java/com/github/copilot/sdk/CopilotSession.java

- Use Double Check Locked to fix Big Risk #2 2. Lazy `SessionRpc` initialization is not thread-safe (HIGH)

modified:   src/test/java/com/github/copilot/sdk/ForwardCompatibilityTest.java
modified:   src/test/java/com/github/copilot/sdk/SessionEventDeserializationTest.java

- Refine tests based on changes.

Signed-off-by: Ed Burns <edburns@microsoft.com>

On branch copilot/add-classical-code-gen-workflow-ready-for-review
modified:   pom.xml

- Add profiles for generating code and updating the schemas from which the code is generated.

modified:   scripts/codegen/java.ts

- Address copilot comment:

> requiredSet is computed but never used in renderNestedType(), which makes the generator harder to maintain (and can confuse future changes around nullability/boxing). Remove it or use it to drive required-vs-optional component typing if that’s the intent.

Signed-off-by: Ed Burns <edburns@microsoft.com>

Add AI code review to update-copilot-dependency workflow
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
modified:   scripts/codegen/java.ts

### 18:24 Prompt

Consider these Copilot review comments, all about `java.ts`

- Lines 167 - 178

> The generator passes required=true when generating container element/value types (List<T>, Map<String, V>). This can produce illegal Java generic types like List<long> / Map<String, boolean> when the schema item type is integer/boolean/number, causing generated code not to compile. Fix by ensuring container element/value types are always boxed (e.g., call schemaTypeToJava(..., false, ...) for items and additionalProperties value schemas, or add a dedicated “box primitives in generics” step).

### 18:29 Prompt

Consider these Copilot review comments, all about `java.ts`

- Lines 193 - 201

> The generator passes required=true when generating container element/value types (List<T>, Map<String, V>). This can produce illegal Java generic types like List<long> / Map<String, boolean> when the schema item type is integer/boolean/number, causing generated code not to compile. Fix by ensuring container element/value types are always boxed (e.g., call schemaTypeToJava(..., false, ...) for items and additionalProperties value schemas, or add a dedicated “box primitives in generics” step).

### 18:31 Prompt

Consider these Copilot review comments, all about `java.ts`

- Lines 1258 - 1261

> RpcMapper.INSTANCE is a plain new ObjectMapper() with no modules. Generated RPC DTOs can include OffsetDateTime (your type mapping emits it for format: date-time), and ObjectMapper.valueToTree(...) used by session wrappers can fail without JavaTimeModule registered. Fix by configuring this shared mapper consistently (e.g., register com.fasterxml.jackson.datatype.jsr310.JavaTimeModule, and align any other ObjectMapper features needed by the SDK).

### 18:34 Prompt

Consider these Copilot review comments, all about `java.ts`

- Lines 1202 - 1207

> This comment is likely incorrect/misleading: Java lambdas can generally target a functional interface whose single abstract method is generic (the compiler can still infer the type parameter from the call site). Consider removing this claim or rephrasing it to a neutral recommendation (e.g., “method reference is typical/clear”) so consumers aren’t discouraged from valid usage.

Copilot suggests:

```typescript
  lines.push(` * (e.g., a {@code JsonRpcClient} instance). A method reference is typically the clearest`);
    lines.push(` * way to adapt a generic {@code invoke} method to this interface:`);
    lines.push(` * <pre>{@code`);
    lines.push(` * RpcCaller caller = jsonRpcClient::invoke;`);
    lines.push(` * }</pre>`);
```

What can we do about this?

### 18:36 Prompt

Consider these Copilot review comments, all about `java.ts`

- Lines 848 - 854

> For session-scoped RPC methods with additional params, the generated *Params records still include a sessionId field (because they’re generated directly from the schema), but the wrapper then overwrites sessionId via _p.put("sessionId", this.sessionId). This duplication is confusing for API consumers and makes the param records look “callable” with arbitrary session IDs when they are not. Consider adjusting generation so sessionId is omitted from session-scoped params records (and only injected by SessionRpc), or documenting clearly in the generated Javadoc that any provided sessionId is ignored/overridden.

Copilot suggests:

```typescript
 * Return the wrapper-visible parameter property names for a method.
 * For session-scoped wrappers, sessionId is injected by SessionRpc and is not
 * considered a user-supplied parameter.
 */
function wrapperParamPropertyNames(method: RpcMethodNode, isSession: boolean): string[] {
    if (!method.params || typeof method.params !== "object") return [];
    const props = method.params.properties ?? {};
    return Object.keys(props).filter((k) => !(isSession && k === "sessionId"));
}
/**
 * Return the params class name if the method has wrapper-visible properties
 * (i.e. user-supplied parameters after filtering out injected sessionId for
 * session-scoped wrappers).
 */
function wrapperParamsClassName(method: RpcMethodNode, isSession: boolean): string | null {
    const userProps = wrapperParamPropertyNames(method, isSession);
```

What can we do about this?

### 18:40 Prompt

Consider these Copilot review comments, all about `java.ts`

- Lines 903 - 915

> For session-scoped RPC methods with additional params, the generated *Params records still include a sessionId field (because they’re generated directly from the schema), but the wrapper then overwrites sessionId via _p.put("sessionId", this.sessionId). This duplication is confusing for API consumers and makes the param records look “callable” with arbitrary session IDs when they are not. Consider adjusting generation so sessionId is omitted from session-scoped params records (and only injected by SessionRpc), or documenting clearly in the generated Javadoc that any provided sessionId is ignored/overridden.

What can we do about this? This seems the same as the previous comment. No?

modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/RpcCaller.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/RpcMapper.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionAgentApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionCommandsApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionExtensionsApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionFleetApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionHistoryApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionMcpApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionModeApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionModelApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionPermissionsApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionPlanApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionRpc.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionShellApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionSkillsApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionToolsApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionUiApi.java
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/SessionWorkspaceApi.java

- Regenerated.

Signed-off-by: Ed Burns <edburns@microsoft.com>

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
modified:   scripts/codegen/java.ts
modified:   src/generated/java/com/github/copilot/sdk/generated/rpc/RpcMapper.java

Clean run. The fix was: RpcMapper now constructs its own ObjectMapper with the same configuration as JsonRpcClient's mapper, instead of trying to call JsonRpcClient.getObjectMapper() across packages (which failed because JsonRpcClient is package-private).

Signed-off-by: Ed Burns <edburns@microsoft.com>

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
…est-thread wakeup mid-dispatch

Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/e7c09bc7-4fc9-4619-a0d9-d1bed96104b9

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
…ilitiesOverride, agent skills, per-request headers

Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/514da8aa-3336-46ca-b39a-48faabcbb354

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
When the CLI sends hooks.invoke or permission.request with a session ID
that is not registered (e.g. for CLI-created sub-agent sessions), fall
back to a registered session that has the appropriate handler. If no
session with a handler exists, hooks returns null output (no-op) and
permissions returns denied.

Agent-Logs-Url: https://github.com/github/copilot-sdk-java/sessions/7e1546a6-1f1c-4d86-b564-5d98d7586407

Co-authored-by: edburns <75821+edburns@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 21, 2026

@copilot This PR shows 0 files changed. Rebase to HEAD of main and then produce new commits to implement the original intent.

Rebased to HEAD of main and implemented the sub-agent fallback in ba9c9e0:

  • handleHooksInvoke: when the session ID is unknown, falls back to a registered session that has hooks handlers; returns null output (no-op) if none exists
  • handlePermissionRequest: when the session ID is unknown, falls back to a registered session that has a permission handler; returns denied if none exists
  • Added hasHooksHandler() and hasPermissionHandler() query methods to CopilotSession
  • Added tests for both fallback and no-fallback scenarios

Copilot AI changed the title Document upstream SDK behavior for sub-agent hook/permission dispatch Fix hooks and permission dispatch for sub-agent sessions Apr 21, 2026
Copilot AI requested a review from edburns April 21, 2026 15:26
@edburns
Copy link
Copy Markdown
Collaborator

edburns commented Apr 21, 2026

The work on this PR has been overtaken by other PRs to main. I will ask the agent to start over with a fresh PR, referencing this one for history.

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.

[BUG]: Sub-agent tool calls bypass SDK session hooks and permission handlers (was: onPreToolUse hook does not fire for sub-agent tool calls)

4 participants