github / github/copilot-sdk

Extension SessionHooks missing onPostToolUseFailure handler — failed tool results are invisible

Aperta
#1,220 4 commenti 0 reazioni 1 assegnatario Rivendicata da @patniko Vedi su GitHub
bug
Lingua principale
Java
Stelle
10.5k
Fork
1.5k
Merge medio
1g 11h
PR unite (30g)
128

Descrizione

## Summary

Extensions registering hook handlers via `joinSession({ hooks })` cannot observe non-success tool results. `onPostToolUse` only fires for `resultType === "success"` — failure / rejected / denied / timeout outcomes are silently invisible to extensions.

The underlying CLI does emit a `postToolUseFailure` hook event for failed tool calls, but the SDK never dispatches it: it is missing from both the `SessionHooks` interface and the runtime `_handleHooksInvoke` handler map.

## Repro

1. In an extension, register a session via `joinSession` with handlers for both `onPostToolUse` and (attempting) `onPostToolUseFailure`:

```js
import { joinSession } from "@github/copilot-sdk/extension";
const session = await joinSession({
hooks: {
onPostToolUse: async (input) => {
console.log("post:", input.toolName, input.toolResult?.resultType);
},
// Not declared on SessionHooks, but try anyway:
onPostToolUseFailure: async (input) => {
console.log("post-failure:", input.toolName);
},
},
});
```

2. Have Copilot run a tool that fails (e.g. a shell command that exits non-zero, then a tool that times out, then one that triggers a "rejected" / "denied" outcome).

3. Observe: `onPostToolUse` fires only for the successful tool calls. `onPostToolUseFailure` never fires for any of the failure modes.

## Expected

Extensions should be able to observe **all** terminal tool outcomes — success, failure, rejected, denied, timeout — through the hooks system. Either:

- Add `onPostToolUseFailure` to `SessionHooks` and dispatch it from `_handleHooksInvoke`, or
- Always fire `onPostToolUse` regardless of `resultType` (with the `resultType` field on the input distinguishing outcomes).

## Evidence in shipped CLI `1.0.43-0`

Path: `/copilot-sdk/extension.js` (the bundled SDK shipped with the Copilot CLI).

The runtime hook dispatcher exposes only 6 hook types:

```js
async _handleHooksInvoke(hookType, input) {
if (!this.hooks) return void 0;
const handlerMap = {
preToolUse: this.hooks.onPreToolUse,
postToolUse: this.hooks.onPostToolUse,
userPromptSubmitted: this.hooks.onUserPromptSubmitted,
sessionStart: this.hooks.onSessionStart,
sessionEnd: this.hooks.onSessionEnd,
errorOccurred: this.hooks.onErrorOccurred,
};
const handler = handlerMap[hookType];
if (!handler) return void 0; // silently dropped
...
}
```

The matching TypeScript declaration in `copilot-sdk/types.d.ts` `SessionHooks` declares the same 6 handlers, so authors using TypeScript can't even discover the missing types.

Meanwhile, the host CLI (`app.js` in the same package) builds a hook proxy per connected extension that DOES include `postToolUseFailure`:

```js
// createHooksProxy(sessionId, connection)
return {
preToolUse: [o => n("preToolUse", o)],
postToolUse: [o => n("postToolUse", o)],
postToolUseFailure: [o => n("postToolUseFailure", o)],
userPromptSubmitted: [o => n("userPromptSubmitted", o)],
sessionStart: [o => n("sessionStart", o)],
sessionEnd: [o => n("sessionEnd", o)],
errorOccurred: [o => n("errorOccurred", o)],
};
```

And the host's `processToolExecutionResult` explicitly gates `postToolUse` on success, routing failures elsewhere:

```js
async processToolExecutionResult(toolName, toolArgs, toolResult) {
let s = (toolResult.resultType === "success"
? await h1(this.getEffectiveHooks()?.postToolUse, ...)
: void 0)?.modifiedResult ?? toolResult;

let a = s.resultType === "failure"
? await this.runPostToolUseFailureHooks(toolName, toolArgs, s)
: void 0;
...
}
```

So the CLI sends `hooks.invoke` requests with `hookType: "postToolUseFailure"` to the extension over IPC, but the SDK's `_handleHooksInvoke` doesn't recognize that hook type and returns `undefined` without invoking any handler.

The full set of hook types the CLI knows about (`app.js`):

```js
new Set([
"sessionStart", "sessionEnd", "userPromptSubmitted",
"preToolUse", "postToolUse", "postToolUseFailure",
"errorOccurred",
"agentStop", "subagentStop", "subagentStart",
"preCompact", "permissionRequest", "notification",
])
```

The SDK's hooks API exposes 6 of these to extensions. Several of the missing ones (like `permissionRequest`) are intentionally exposed via different APIs (`onPermissionRequest` in the `JoinSessionConfig`), so this report intentionally focuses on the most clear-cut gap: `postToolUseFailure`.

## Impact

Extensions that want to observe or react to tool failures — for telemetry, replay buffers, fault-injection tests, or UI that visualizes tool execution — have no reliable way to do so. Since post-only-on-success silently drops the failure cases, extensions can wrongly report "no result" or, if pairing pre with post, leak resources tracking calls whose post will never come.

## Suggested fix

Add the missing handler to `_handleHooksInvoke`'s map and to the `SessionHooks` TypeScript interface:

```diff
export interface SessionHooks {
onPreToolUse?: PreToolUseHandler;
onPostToolUse?: PostToolUseHandler;
+ onPostToolUseFailure?: PostToolUseFailureHandler;
onUserPromptSubmitted?: UserPromptSubmittedHandler;
onSessionStart?: SessionStartHandler;
onSessionEnd?: SessionEndHandler;
onErrorOccurred?: ErrorOccurredHandler;
}
```

```diff
const handlerMap = {
preToolUse: this.hooks.onPreToolUse,
postToolUse: this.hooks.onPostToolUse,
+ postToolUseFailure: this.hooks.onPostToolUseFailure,
userPromptSubmitted: this.hooks.onUserPromptSubmitted,
sessionStart: this.hooks.onSessionStart,
sessionEnd: this.hooks.onSessionEnd,
errorOccurred: this.hooks.onErrorOccurred,
};
```

The input shape can mirror `PostToolUseHookInput` (`toolName` + `toolArgs` + `toolResult`), since the CLI's emit site already provides the same payload.

## Environment

- `@github/copilot` `1.0.43-0` (Windows `win32-x64` package).
- Bundled SDK at `/copilot-sdk/extension.js`.
- Confirmed by inspecting the shipped JS and the TypeScript declarations in the same package.

Guida per i contributori

Apri la guida per i contributori

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.