Copilot SDK Error Handling and Recovery with the onErrorOccurred Hook

The Copilot SDK provides an onErrorOccurred hook that allows extensions to intercept runtime failures—such as model errors or connection issues—and control recovery behavior by choosing to retry, skip, or abort operations while optionally suppressing default error notifications.

The github/copilot-sdk exposes a flexible hook-based error handling mechanism that decouples error detection from recovery logic. By implementing the onErrorOccurred handler during session initialization, developers can customize how their Copilot extensions respond to RPC failures, tool execution errors, and transient network issues without modifying the core SDK internals.

Hook Type Definitions in types.ts

The SDK declares the complete contract for error handling in nodejs/src/types.ts. This file defines the input payload, response options, and handler signature that govern how errors flow through the system.

ErrorOccurredHookInput Structure

When an error bubbles up, the runtime constructs an ErrorOccurredHookInput object containing diagnostic details. According to the source code at line 1500, this interface includes the original error object, a short error name, and the stack trace, giving handlers full context about the failure.

ErrorOccurredHookOutput Recovery Options

Handlers return an ErrorOccurredHookOutput object (defined at line 1504) to influence the session’s next action. This output supports four key properties:

  • suppressOutput – Set to true to hide the SDK’s default error message UI.
  • errorHandling – A string enum accepting "retry", "skip", or "abort" to determine the recovery strategy.
  • retryCount – An integer specifying how many automatic retry attempts the runtime should execute.
  • userNotification – A custom string displayed to the user in place of the generic error message.

ErrorOccurredHandler Signature

The actual handler function follows the ErrorOccurredHandler type defined at line 1512. This is an async function receiving two arguments: the ErrorOccurredHookInput and a context object containing { sessionId: string }. It may return an ErrorOccurredHookOutput or void. If the handler returns nothing, the SDK falls back to its default behavior: logging the error, displaying a generic message, and aborting the current turn.

SessionHooks Registration

Extensions register the handler through the SessionHooks interface at line 1609. The onErrorOccurred property is optional, allowing sessions to opt-in to custom error handling only when needed.

Runtime Invocation in session.ts

The session runtime monitors RPC calls, tool invocations, and internal processing for uncaught exceptions. When a ResponseError, ConnectionError, or standard exception is detected, the runtime constructs the hook input and dispatches it to the registered handler.

In nodejs/src/session.ts around line 1884, the session invokes the hook using optional chaining:

this.hooks.onErrorOccurred?.(errorInput, { sessionId: this.id });

This dispatch occurs immediately after error normalization but before the runtime commits to any recovery action, ensuring the handler’s preferences are respected.

Controlling Error Recovery and UX

The onErrorOccurred hook provides granular control over both the technical recovery path and the user experience. Developers can implement domain-specific policies such as exponential back-off for network errors or graceful degradation for unavailable tools.

Suppressing Default Error Output

By returning { suppressOutput: true } from the handler, extensions prevent the SDK from rendering its generic error UI. This is essential when providing custom error visualization or when errors are expected and should not alarm the user.

Choosing Recovery Strategies

The errorHandling field lets extensions dictate the runtime’s next move:

  • "retry" – The runtime re-executes the failed operation, respecting the retryCount limit.
  • "skip" – The runtime continues execution, bypassing the failed step entirely.
  • "abort" – The runtime terminates the current session or turn immediately.

Configuring Retry Logic

When handling transient failures such as network timeouts or temporary model unavailability, the retryCount property specifies the maximum number of attempts. The SDK automatically manages the retry loop based on this value, eliminating the need for manual retry logic in the extension.

Custom User Notifications

The userNotification field propagates a developer-defined string to the UI layer. This allows extensions to translate technical errors into actionable messages, such as "Connection lost. Reconnecting..." instead of raw stack traces.

Practical Implementation Example

The SDK’s end-to-end test suite demonstrates a typical usage pattern in nodejs/test/e2e/hooks_extended.e2e.test.ts. The test registers an onErrorOccurred hook that inspects the error name and applies conditional recovery logic:

// Excerpt from lines 110-120
await runSession({
  onErrorOccurred: async (input, invocation) => {
    if (input.error?.name === "ModelError") {
      return { errorHandling: "retry", retryCount: 2 };
    }
    return { errorHandling: "abort", userNotification: "Fatal error" };
  },
});

In this example, the extension retries model errors twice before giving up, while immediately aborting other error types with a custom notification.

Error Propagation Architecture

The Copilot SDK implements a five-stage pipeline that separates error detection from recovery:

  1. Error Generation – An RPC call in nodejs/src/client.ts, tool execution, or internal component throws an exception.
  2. Error Normalisation – The runtime wraps the raw error into an ErrorOccurredHookInput with standardized fields.
  3. Hook Dispatch – The session runtime checks for this.hooks.onErrorOccurred and invokes it with the normalized input and session metadata.
  4. Handler Response – The extension’s handler analyzes the error and returns an ErrorOccurredHookOutput specifying retry, skip, or abort instructions.
  5. Runtime Action – Based on the response, the session either retries the operation (up to retryCount times), continues without the failed step, or terminates, optionally showing the userNotification.

This design keeps low-level connection logic in nodejs/src/client.ts while allowing high-level policy decisions to reside in the extension code.

Summary

  • The onErrorOccurred hook in github/copilot-sdk lets extensions intercept errors at nodejs/src/session.ts line 1884 before the runtime commits to a recovery strategy.
  • Handler return values in nodejs/src/types.ts control suppression of default UI, retry attempts via retryCount, and the choice between retrying, skipping, or aborting failed operations.
  • The hook receives detailed context through ErrorOccurredHookInput including the original error, name, and stack trace.
  • Custom userNotification strings allow extensions to replace technical error messages with user-friendly guidance.
  • If no handler is registered or it returns void, the SDK defaults to logging the error, showing a generic message, and aborting the turn.

Frequently Asked Questions

What types of errors trigger the onErrorOccurred hook?

The hook fires for any uncaught exception during the session lifecycle, including ResponseError and ConnectionError from the JSON-RPC layer in nodejs/src/client.ts, model execution failures, and tool invocation exceptions. The runtime normalizes all these into an ErrorOccurredHookInput before dispatching to the handler.

How does the retry mechanism work with retryCount?

When a handler returns { errorHandling: "retry", retryCount: 2 }, the SDK re-executes the failed operation up to two additional times. The runtime manages the retry loop internally, checking the count before each attempt. If all retries fail, the error propagates as though no retry was attempted, unless the handler updates its strategy in subsequent invocations.

Can I suppress the default error UI completely?

Yes. Returning { suppressOutput: true } from the onErrorOccurred handler prevents the SDK from rendering its default error interface. This is useful when extensions implement their own error visualization or when certain error conditions are expected and should remain invisible to the user.

What happens if the onErrorOccurred handler throws an exception?

If the handler itself throws, the SDK catches the exception and falls back to default error handling: logging the original error, displaying the generic error UI, and aborting the current turn. The handler exception is logged separately for debugging purposes, but it does not crash the session or prevent cleanup.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →