{"record":{"id":"c5f4ebe28bbaacfb","repo":"github/copilot-sdk","slug":"exit-plan-mode-handler-error","errorCode":null,"errorMessage":"Exit plan mode handler error","messagePattern":"Exit plan mode handler error","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"java/sdk/src/main/java/com/github/copilot/CopilotSession.java","lineNumber":1810,"sourceCode":"     * Handles an exit-plan-mode request from the Copilot CLI.\n     * <p>\n     * Called internally when the server sends an {@code exitPlanMode.request}.\n     *\n     * @param request\n     *            the exit-plan-mode request\n     * @return a future that resolves with the user's decision\n     */\n    CompletableFuture<ExitPlanModeResult> handleExitPlanModeRequest(ExitPlanModeRequest request) {\n        ExitPlanModeHandler handler = exitPlanModeHandler.get();\n        if (handler == null) {\n            return CompletableFuture.completedFuture(new ExitPlanModeResult().setApproved(true));\n        }\n\n        try {\n            var invocation = new ExitPlanModeInvocation().setSessionId(sessionId);\n            return handler.handle(request, invocation).exceptionally(ex -> {\n                LOG.log(Level.SEVERE, \"Exit plan mode handler threw an exception\", ex);\n                throw new RuntimeException(\"Exit plan mode handler error\", ex);\n            });\n        } catch (Exception e) {\n            LOG.log(Level.SEVERE, \"Failed to process exit plan mode request\", e);\n            return CompletableFuture.failedFuture(e);\n        }\n    }\n\n    /**\n     * Handles an auto-mode-switch request from the Copilot CLI.\n     * <p>\n     * Called internally when the server sends an {@code autoModeSwitch.request}.\n     *\n     * @param request\n     *            the auto-mode-switch request\n     * @return a future that resolves with the user's decision\n     */\n    CompletableFuture<AutoModeSwitchResponse> handleAutoModeSwitchRequest(AutoModeSwitchRequest request) {\n        AutoModeSwitchHandler handler = autoModeSwitchHandler.get();","sourceCodeStart":1792,"sourceCodeEnd":1828,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/java/sdk/src/main/java/com/github/copilot/CopilotSession.java#L1792-L1828","documentation":"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.","triggerScenarios":"The handler registered for ExitPlanModeInvocation (ExitPlanModeHandler.handle) throws, or the CompletableFuture returned from handle() completes exceptionally (CopilotSession.java:1810).","commonSituations":"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.","solutions":["Read the cause of the wrapped exception and fix the failing logic in your ExitPlanModeHandler implementation.","Guard state access in the handler against a session that may already be closing or terminated.","Return a failed future with a domain-specific exception instead of letting raw exceptions propagate for better logs."],"exampleFix":"// before\n(request, invocation) -> saveState(invocation.getSessionId(), plan); // throws if plan null\n// after\n(request, invocation) -> {\n    if (plan == null) {\n        return CompletableFuture.completedFuture(null);\n    }\n    return saveState(invocation.getSessionId(), plan);\n};","handlingStrategy":"try-catch","validationCode":"if (plan == null || invocation.getSessionId() == null) {\n    return CompletableFuture.completedFuture(null);\n}","typeGuard":"boolean canPersist(Plan plan) { return plan != null && plan.steps() != null; }","tryCatchPattern":"try {\n    return handler.handle(request, invocation);\n} catch (Exception e) {\n    LOG.warning(\"exit plan mode failed: \" + e.getMessage());\n    return CompletableFuture.failedFuture(e);\n}","preventionTips":["Assume the session may be closing when the handler runs; guard state access.","Keep persistence logic in the handler fault-tolerant (best-effort writes).","Test your handler with a session close racing the plan-exit flow."],"tags":["handler-exception","plan-mode","rpc"],"backgroundTag":"handler-exception","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}