Skip to content

fix: add error isolation to prevent WebUI crashes, server disconnections, and agent stalling#32

Merged
heidi-dang merged 1 commit into
mainfrom
devin/1773321384-fix-webui-crash-agent-stall
Mar 12, 2026
Merged

fix: add error isolation to prevent WebUI crashes, server disconnections, and agent stalling#32
heidi-dang merged 1 commit into
mainfrom
devin/1773321384-fix-webui-crash-agent-stall

Conversation

@heidi-dang
Copy link
Copy Markdown
Owner

Summary

  • Adds error isolation across the plugin event dispatch pipeline so a single hook failure no longer crashes the entire event handler, preventing WebUI disconnections and agent stalls.
  • Catches unhandled promise rejections in setTimeout callbacks that could crash the process.

Changes

  • src/plugin/event.ts: Introduced safeHookCall wrapper — each hook in dispatchToHooks now runs inside its own try/catch so one failure doesn't break the chain. Added outer try/catch around dispatchToHooks call and the session.created block (which was unprotected, unlike session.deleted).
  • src/plugin/tool-execute-before.ts: Added top-level try/catch, matching the existing pattern in tool-execute-after.ts and chat-message.ts.
  • src/hooks/session-notification-scheduler.ts: Added .catch() to the executeNotification() call inside setTimeout to prevent unhandled promise rejections.
  • src/hooks/runtime-fallback/auto-retry.ts: Wrapped the async setTimeout callback in scheduleSessionFallbackTimeout with try/catch to prevent unhandled rejections from prepareFallback/autoRetryWithFallback.

Human Review Checklist

⚠️ tool-execute-before.ts is missing the isSafetyCriticalHookError re-throw. tool-execute-after.ts has a carve-out that re-throws safety-critical errors (planEnforcement, semanticLoopGuard, etc. run here). The new catch block swallows all errors — verify this is intentional or add the same guard.

⚠️ session-notification-scheduler.ts .catch(() => {}) silently swallows errors with no logging. Consider adding a log call inside the catch.

Worth verifying that session.created partial execution (e.g., setMainSession succeeds but markSessionCreated throws) doesn't leave inconsistent state.

Testing

bun run typecheck

No lint script or test suite was found for targeted verification. Changes are structural error-handling additions — all existing behavior is preserved; errors are now caught and logged instead of propagating.

Related Issues


Link to Devin session: https://app.devin.ai/sessions/2d0abdf28ca74ce4856d1b642ea5d35a
Requested by: @heidi-dang

…ons, and agent stalling

- event.ts: Wrap each hook call in dispatchToHooks with individual try/catch
  so one hook failure doesn't break the entire dispatch chain
- event.ts: Add try/catch around dispatchToHooks call in main handler so
  session.created/deleted/error handling always runs
- event.ts: Add try/catch around session.created block to prevent tracking
  failures from crashing the event handler
- tool-execute-before.ts: Add top-level try/catch matching the pattern in
  tool-execute-after.ts and chat-message.ts
- session-notification-scheduler.ts: Add .catch() to executeNotification
  in setTimeout to prevent unhandled promise rejections
- auto-retry.ts: Add top-level try/catch in scheduleSessionFallbackTimeout
  setTimeout callback to prevent unhandled rejections from prepareFallback

Co-Authored-By: Huy Nguyen <[email protected]>
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the stability and resilience of the system by introducing comprehensive error isolation and handling across critical event dispatch and tool execution pathways. The changes ensure that transient errors in individual plugin hooks or asynchronous operations within setTimeout callbacks are gracefully caught and logged, preventing cascading failures that could lead to WebUI crashes, server disconnections, or agent stalling. The focus is on making the system more robust without altering existing functional behavior.

Highlights

  • Enhanced Error Isolation in Plugin Event Dispatch: Implemented a safeHookCall wrapper to ensure that individual hook failures within the plugin event dispatch pipeline do not crash the entire event handler. This prevents WebUI disconnections and agent stalls by isolating errors.
  • Unhandled Promise Rejection Prevention: Added mechanisms to catch unhandled promise rejections in setTimeout callbacks, specifically in session fallback and notification scheduling, to prevent process crashes.
  • Comprehensive Error Handling for Event and Tool Execution: Introduced top-level try/catch blocks around the main dispatchToHooks function, the session.created event handler, and the tool.execute.before handler to provide robust error capture and logging.
Changelog
  • src/hooks/runtime-fallback/auto-retry.ts
    • Wrapped the asynchronous callback within setTimeout in a try/catch block to handle potential errors during session fallback and prevent unhandled promise rejections.
  • src/hooks/session-notification-scheduler.ts
    • Appended a .catch(() => {}) to the executeNotification() call inside setTimeout to prevent unhandled promise rejections, silently swallowing errors.
  • src/plugin/event.ts
    • Introduced a safeHookCall utility function to wrap individual hook invocations, ensuring each hook runs within its own try/catch block to isolate failures.
    • Modified all hook calls within dispatchToHooks to use the new safeHookCall wrapper.
    • Added an outer try/catch block around the dispatchToHooks call to catch any unhandled errors during the overall event dispatch process.
    • Enclosed the session.created event handling logic within a try/catch block to protect against errors during session initialization.
  • src/plugin/tool-execute-before.ts
    • Added a top-level try/catch block around the entire tool.execute.before handler to catch and log any unhandled errors during tool execution pre-processing.
Activity
  • The author, heidi-dang, requested this pull request.
  • Initial review points raised by the author include verifying the intentional swallowing of errors in tool-execute-before.ts and session-notification-scheduler.ts, and checking for inconsistent state after partial session.created execution.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces error isolation to prevent crashes from unhandled exceptions in various parts of the plugin event pipeline. The changes are generally good, wrapping asynchronous operations and hook dispatches in try/catch blocks to improve stability. My review includes a few suggestions:

  • Log errors instead of silently swallowing them in one of the setTimeout handlers.
  • Ensure consistent error object logging.
  • A high-severity concern about swallowing all errors in tool-execute-before.ts, where some critical errors should likely be re-thrown to halt execution, as noted in the PR's own review checklist.

Comment on lines +116 to 118
} catch (err: unknown) {
log("[tool-execute-before.ts] Unhandled hook error caught:", { error: err instanceof Error ? err.message : String(err) })
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

As noted in the PR description, this catch block swallows all errors, which might not be desirable for safety-critical hooks like planEnforcement and semanticLoopGuard. To avoid unintentionally suppressing important failures, consider re-throwing errors that are deemed critical, similar to the pattern used in tool-execute-after.ts.

For example:

} catch (err: unknown) {
  if (isSafetyCriticalHookError(err)) { // `isSafetyCriticalHookError` would need to be defined/imported
    throw err;
  }
  log("[tool-execute-before.ts] Unhandled hook error caught:", { error: err instanceof Error ? err.message : String(err) })
}


const timer = setTimeout(() => {
executeNotification(sessionID, currentVersion)
executeNotification(sessionID, currentVersion).catch(() => {})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Silently swallowing errors can make debugging difficult. It's better to log the error to aid in troubleshooting potential issues with notifications.

You'll also need to import the log function at the top of the file:
import { log } from "../shared/logger";

Suggested change
executeNotification(sessionID, currentVersion).catch(() => {})
executeNotification(sessionID, currentVersion).catch((err) => log(`[session-notification-scheduler] Error executing notification:`, { sessionID, error: String(err) }))

Comment thread src/plugin/event.ts
},
);
} catch (err) {
log("[event] Error in session.created handler:", { error: err });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For consistency with other error logging in this file and to prevent potential issues with logging complex error objects, it's better to serialize the error to a string.

Suggested change
log("[event] Error in session.created handler:", { error: err });
log("[event] Error in session.created handler:", { error: String(err) });

@heidi-dang heidi-dang merged commit 60accc2 into main Mar 12, 2026
1 check passed
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.

1 participant