github/copilot-sdk · error · RuntimeException

Exit plan mode handler error

Error message

Exit plan mode handler error

What it means

CopilotSession invokes the registered exit-plan-mode handler and converts any synchronous or asynchronous failure in that handler into a RuntimeException with the message 'Exit plan mode handler error', logging the original at SEVERE. The failure originates in the user-supplied handler, not the SDK transport itself.

Solutions

  1. Read the cause of the wrapped exception and fix the failing logic in your ExitPlanModeHandler implementation.
  2. Guard state access in the handler against a session that may already be closing or terminated.
  3. Return a failed future with a domain-specific exception instead of letting raw exceptions propagate for better logs.

Example fix

// before
(request, invocation) -> saveState(invocation.getSessionId(), plan); // throws if plan null
// after
(request, invocation) -> {
    if (plan == null) {
        return CompletableFuture.completedFuture(null);
    }
    return saveState(invocation.getSessionId(), plan);
};
Defensive patterns

Strategy: try-catch

Validate before calling

if (plan == null || invocation.getSessionId() == null) {
    return CompletableFuture.completedFuture(null);
}

Type guard

boolean canPersist(Plan plan) { return plan != null && plan.steps() != null; }

Try / catch

try {
    return handler.handle(request, invocation);
} catch (Exception e) {
    LOG.warning("exit plan mode failed: " + e.getMessage());
    return CompletableFuture.failedFuture(e);
}

Prevention

When it happens

Trigger: The handler registered for ExitPlanModeInvocation (ExitPlanModeHandler.handle) throws, or the CompletableFuture returned from handle() completes exceptionally (CopilotSession.java:1810).

Common situations: An exit-plan-mode handler tries to persist plan state to disk or a database and the write fails, or it accesses session state that has already been torn down when the user exits plan mode.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/c5f4ebe28bbaacfb. Report an issue: GitHub.

Appendix: source

Thrown at java/sdk/src/main/java/com/github/copilot/CopilotSession.java:1810

     * Handles an exit-plan-mode request from the Copilot CLI.
     * <p>
     * Called internally when the server sends an {@code exitPlanMode.request}.
     *
     * @param request
     *            the exit-plan-mode request
     * @return a future that resolves with the user's decision
     */
    CompletableFuture<ExitPlanModeResult> handleExitPlanModeRequest(ExitPlanModeRequest request) {
        ExitPlanModeHandler handler = exitPlanModeHandler.get();
        if (handler == null) {
            return CompletableFuture.completedFuture(new ExitPlanModeResult().setApproved(true));
        }

        try {
            var invocation = new ExitPlanModeInvocation().setSessionId(sessionId);
            return handler.handle(request, invocation).exceptionally(ex -> {
                LOG.log(Level.SEVERE, "Exit plan mode handler threw an exception", ex);
                throw new RuntimeException("Exit plan mode handler error", ex);
            });
        } catch (Exception e) {
            LOG.log(Level.SEVERE, "Failed to process exit plan mode request", e);
            return CompletableFuture.failedFuture(e);
        }
    }

    /**
     * Handles an auto-mode-switch request from the Copilot CLI.
     * <p>
     * Called internally when the server sends an {@code autoModeSwitch.request}.
     *
     * @param request
     *            the auto-mode-switch request
     * @return a future that resolves with the user's decision
     */
    CompletableFuture<AutoModeSwitchResponse> handleAutoModeSwitchRequest(AutoModeSwitchRequest request) {
        AutoModeSwitchHandler handler = autoModeSwitchHandler.get();

View on GitHub (pinned to cd8cf15dc3)