|
| 1 | +--- |
| 2 | +title: Error handling hook |
| 3 | +shortTitle: Error handling |
| 4 | +intro: 'Use the `onErrorOccurred` hook to implement custom error logging, track error patterns, and provide user-friendly error messages in {% data variables.copilot.copilot_sdk_short %}.' |
| 5 | +product: '{% data reusables.gated-features.copilot-sdk %}' |
| 6 | +versions: |
| 7 | + feature: copilot |
| 8 | +contentType: how-tos |
| 9 | +category: |
| 10 | + - Author and optimize with Copilot |
| 11 | +--- |
| 12 | + |
| 13 | +> [!NOTE] |
| 14 | +> {% data reusables.copilot.copilot-sdk.technical-preview-note %} |
| 15 | +
|
| 16 | +The `onErrorOccurred` hook is called when errors occur during session execution. Use it to: |
| 17 | + |
| 18 | +* Implement custom error logging |
| 19 | +* Track error patterns |
| 20 | +* Provide user-friendly error messages |
| 21 | +* Trigger alerts for critical errors |
| 22 | + |
| 23 | +## Hook signature |
| 24 | + |
| 25 | +```typescript |
| 26 | +import type { ErrorOccurredHookInput, HookInvocation, ErrorOccurredHookOutput } from "@github/copilot-sdk"; |
| 27 | +type ErrorOccurredHandler = ( |
| 28 | + input: ErrorOccurredHookInput, |
| 29 | + invocation: HookInvocation |
| 30 | +) => Promise< |
| 31 | + ErrorOccurredHookOutput | null | undefined |
| 32 | +>; |
| 33 | +``` |
| 34 | + |
| 35 | +For hook signatures in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/error-handling.md#hook-signature). |
| 36 | + |
| 37 | +## Input |
| 38 | + |
| 39 | +| Field | Type | Description | |
| 40 | +|-------|------|-------------| |
| 41 | +| `timestamp` | number | Unix timestamp when the error occurred | |
| 42 | +| `cwd` | string | Current working directory | |
| 43 | +| `error` | string | Error message | |
| 44 | +| `errorContext` | string | Where the error occurred: `"model_call"`, `"tool_execution"`, `"system"`, or `"user_input"` | |
| 45 | +| `recoverable` | boolean | Whether the error can potentially be recovered from | |
| 46 | + |
| 47 | +## Output |
| 48 | + |
| 49 | +Return `null` or `undefined` to use default error handling. Otherwise, return an object with any of the following fields. |
| 50 | + |
| 51 | +| Field | Type | Description | |
| 52 | +|-------|------|-------------| |
| 53 | +| `suppressOutput` | boolean | If true, don't show error output to user | |
| 54 | +| `errorHandling` | string | How to handle: `"retry"`, `"skip"`, or `"abort"` | |
| 55 | +| `retryCount` | number | Number of times to retry (if `errorHandling` is `"retry"`) | |
| 56 | +| `userNotification` | string | Custom message to show the user | |
| 57 | + |
| 58 | +## Examples |
| 59 | + |
| 60 | +### Basic error logging |
| 61 | + |
| 62 | +```typescript |
| 63 | +const session = await client.createSession({ |
| 64 | + hooks: { |
| 65 | + onErrorOccurred: async ( |
| 66 | + input, invocation |
| 67 | + ) => { |
| 68 | + console.error( |
| 69 | + `[${invocation.sessionId}] ` |
| 70 | + + `Error: ${input.error}` |
| 71 | + ); |
| 72 | + console.error( |
| 73 | + ` Context: ${input.errorContext}` |
| 74 | + ); |
| 75 | + console.error( |
| 76 | + ` Recoverable: ${input.recoverable}` |
| 77 | + ); |
| 78 | + return null; |
| 79 | + }, |
| 80 | + }, |
| 81 | +}); |
| 82 | +``` |
| 83 | + |
| 84 | +For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/hooks/error-handling.md#basic-error-logging). |
| 85 | + |
| 86 | +### Send errors to monitoring service |
| 87 | + |
| 88 | +```typescript |
| 89 | +import { captureException } from "@sentry/node"; // or your monitoring service |
| 90 | + |
| 91 | +const session = await client.createSession({ |
| 92 | + hooks: { |
| 93 | + onErrorOccurred: async (input, invocation) => { |
| 94 | + captureException(new Error(input.error), { |
| 95 | + tags: { |
| 96 | + sessionId: invocation.sessionId, |
| 97 | + errorContext: input.errorContext, |
| 98 | + }, |
| 99 | + extra: { |
| 100 | + error: input.error, |
| 101 | + recoverable: input.recoverable, |
| 102 | + cwd: input.cwd, |
| 103 | + }, |
| 104 | + }); |
| 105 | + |
| 106 | + return null; |
| 107 | + }, |
| 108 | + }, |
| 109 | +}); |
| 110 | +``` |
| 111 | + |
| 112 | +### User-friendly error messages |
| 113 | + |
| 114 | +```typescript |
| 115 | +const ERROR_MESSAGES: Record<string, string> = { |
| 116 | + "model_call": |
| 117 | + "There was an issue communicating " |
| 118 | + + "with the AI model. Please try again.", |
| 119 | + "tool_execution": |
| 120 | + "A tool failed to execute. " |
| 121 | + + "Please check your inputs and try again.", |
| 122 | + "system": |
| 123 | + "A system error occurred. " |
| 124 | + + "Please try again later.", |
| 125 | + "user_input": |
| 126 | + "There was an issue with your input. " |
| 127 | + + "Please check and try again.", |
| 128 | +}; |
| 129 | + |
| 130 | +const session = await client.createSession({ |
| 131 | + hooks: { |
| 132 | + onErrorOccurred: async (input) => { |
| 133 | + const friendlyMessage = |
| 134 | + ERROR_MESSAGES[input.errorContext]; |
| 135 | + |
| 136 | + if (friendlyMessage) { |
| 137 | + return { |
| 138 | + userNotification: friendlyMessage, |
| 139 | + }; |
| 140 | + } |
| 141 | + |
| 142 | + return null; |
| 143 | + }, |
| 144 | + }, |
| 145 | +}); |
| 146 | +``` |
| 147 | + |
| 148 | +### Suppress non-critical errors |
| 149 | + |
| 150 | +```typescript |
| 151 | +const session = await client.createSession({ |
| 152 | + hooks: { |
| 153 | + onErrorOccurred: async (input) => { |
| 154 | + // Suppress tool execution errors |
| 155 | + // that are recoverable |
| 156 | + if ( |
| 157 | + input.errorContext === "tool_execution" |
| 158 | + && input.recoverable |
| 159 | + ) { |
| 160 | + console.log( |
| 161 | + `Suppressed recoverable error: ` |
| 162 | + + `${input.error}` |
| 163 | + ); |
| 164 | + return { suppressOutput: true }; |
| 165 | + } |
| 166 | + return null; |
| 167 | + }, |
| 168 | + }, |
| 169 | +}); |
| 170 | +``` |
| 171 | + |
| 172 | +### Add recovery context |
| 173 | + |
| 174 | +```typescript |
| 175 | +const session = await client.createSession({ |
| 176 | + hooks: { |
| 177 | + onErrorOccurred: async (input) => { |
| 178 | + if ( |
| 179 | + input.errorContext === "tool_execution" |
| 180 | + ) { |
| 181 | + return { |
| 182 | + userNotification: |
| 183 | + "The tool failed. Here are some " |
| 184 | + + "recovery suggestions:\n" |
| 185 | + + "- Check if required dependencies " |
| 186 | + + "are installed\n" |
| 187 | + + "- Verify file paths are correct\n" |
| 188 | + + "- Try a simpler approach", |
| 189 | + }; |
| 190 | + } |
| 191 | + |
| 192 | + if ( |
| 193 | + input.errorContext === "model_call" |
| 194 | + && input.error.includes("rate") |
| 195 | + ) { |
| 196 | + return { |
| 197 | + errorHandling: "retry", |
| 198 | + retryCount: 3, |
| 199 | + userNotification: |
| 200 | + "Rate limit hit. Retrying...", |
| 201 | + }; |
| 202 | + } |
| 203 | + |
| 204 | + return null; |
| 205 | + }, |
| 206 | + }, |
| 207 | +}); |
| 208 | +``` |
| 209 | + |
| 210 | +### Track error patterns |
| 211 | + |
| 212 | +```typescript |
| 213 | +interface ErrorStats { |
| 214 | + count: number; |
| 215 | + lastOccurred: number; |
| 216 | + contexts: string[]; |
| 217 | +} |
| 218 | + |
| 219 | +const errorStats = |
| 220 | + new Map<string, ErrorStats>(); |
| 221 | + |
| 222 | +const session = await client.createSession({ |
| 223 | + hooks: { |
| 224 | + onErrorOccurred: async ( |
| 225 | + input, invocation |
| 226 | + ) => { |
| 227 | + const key = |
| 228 | + `${input.errorContext}:` |
| 229 | + + `${input.error.substring(0, 50)}`; |
| 230 | + |
| 231 | + const existing = |
| 232 | + errorStats.get(key) || { |
| 233 | + count: 0, |
| 234 | + lastOccurred: 0, |
| 235 | + contexts: [], |
| 236 | + }; |
| 237 | + |
| 238 | + existing.count++; |
| 239 | + existing.lastOccurred = input.timestamp; |
| 240 | + existing.contexts.push( |
| 241 | + invocation.sessionId |
| 242 | + ); |
| 243 | + |
| 244 | + errorStats.set(key, existing); |
| 245 | + |
| 246 | + // Alert if error is recurring |
| 247 | + if (existing.count >= 5) { |
| 248 | + console.warn( |
| 249 | + `Recurring error detected: ` |
| 250 | + + `${key} (${existing.count} times)` |
| 251 | + ); |
| 252 | + } |
| 253 | + |
| 254 | + return null; |
| 255 | + }, |
| 256 | + }, |
| 257 | +}); |
| 258 | +``` |
| 259 | + |
| 260 | +### Alert on critical errors |
| 261 | + |
| 262 | +```typescript |
| 263 | +const CRITICAL_CONTEXTS = [ |
| 264 | + "system", "model_call", |
| 265 | +]; |
| 266 | + |
| 267 | +const session = await client.createSession({ |
| 268 | + hooks: { |
| 269 | + onErrorOccurred: async ( |
| 270 | + input, invocation |
| 271 | + ) => { |
| 272 | + if ( |
| 273 | + CRITICAL_CONTEXTS.includes( |
| 274 | + input.errorContext |
| 275 | + ) |
| 276 | + && !input.recoverable |
| 277 | + ) { |
| 278 | + await sendAlert({ |
| 279 | + level: "critical", |
| 280 | + message: |
| 281 | + `Critical error in session ` |
| 282 | + + `${invocation.sessionId}`, |
| 283 | + error: input.error, |
| 284 | + context: input.errorContext, |
| 285 | + timestamp: new Date( |
| 286 | + input.timestamp |
| 287 | + ).toISOString(), |
| 288 | + }); |
| 289 | + } |
| 290 | + |
| 291 | + return null; |
| 292 | + }, |
| 293 | + }, |
| 294 | +}); |
| 295 | +``` |
| 296 | + |
| 297 | +## Best practices |
| 298 | + |
| 299 | +* **Always log errors.** Even if you suppress them from users, keep logs for debugging. |
| 300 | +* **Categorize errors.** Use `errorContext` to handle different errors appropriately. |
| 301 | +* **Don't swallow critical errors.** Only suppress errors you're certain are non-critical. |
| 302 | +* **Keep hooks fast.** Error handling shouldn't slow down recovery. |
| 303 | +* **Monitor error patterns.** Track recurring errors to identify systemic issues. |
| 304 | + |
| 305 | +## Further reading |
| 306 | + |
| 307 | +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/use-hooks/quickstart) |
| 308 | +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/use-hooks/session-lifecycle) |
| 309 | +* [AUTOTITLE](/copilot/how-tos/copilot-sdk/use-hooks/pre-tool-use) |
0 commit comments