{"record":{"id":"49c37e23ba728d1b","repo":"github/copilot-sdk","slug":"exitplanmoderesult-serialization-cannot-fail","errorCode":null,"errorMessage":"ExitPlanModeResult serialization cannot fail","messagePattern":"ExitPlanModeResult serialization cannot fail","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"rust/src/session.rs","lineNumber":3097,"sourceCode":"        }\n\n        \"exitPlanMode.request\" => {\n            let params = request\n                .params\n                .as_ref()\n                .cloned()\n                .unwrap_or(Value::Object(serde_json::Map::new()));\n            let data: ExitPlanModeData = match serde_json::from_value(params) {\n                Ok(d) => d,\n                Err(e) => {\n                    warn!(error = %e, \"failed to deserialize exitPlanMode.request params, using defaults\");\n                    ExitPlanModeData::default()\n                }\n            };\n\n            let rpc_result = if let Some(exit_plan_handler) = handlers.exit_plan_mode.as_ref() {\n                let result = exit_plan_handler.handle(sid, data).await;\n                serde_json::to_value(result).expect(\"ExitPlanModeResult serialization cannot fail\")\n            } else {\n                serde_json::json!({ \"approved\": true })\n            };\n            let rpc_response = JsonRpcResponse {\n                jsonrpc: \"2.0\".to_string(),\n                id: request.id,\n                result: Some(rpc_result),\n                error: None,\n            };\n            let _ = client.send_response(&rpc_response).await;\n        }\n\n        \"autoModeSwitch.request\" => {\n            let error_code = request\n                .params\n                .as_ref()\n                .and_then(|p| p.get(\"errorCode\"))\n                .and_then(|v| v.as_str())","sourceCodeStart":3079,"sourceCodeEnd":3115,"githubUrl":"https://github.com/github/copilot-sdk/blob/cd8cf15dc3f9e762615790aaed0a771a0f392755/rust/src/session.rs#L3079-L3115","documentation":"When an exit_plan_mode handler returns a result, the library serializes it to JSON with .expect(), asserting that ExitPlanModeResult always serializes successfully (it is a plain struct of serializable fields). Hitting this panic indicates an invariant violation — e.g. a non-serifiable value (map with non-string keys, NaN) smuggled into the result type.","triggerScenarios":"A custom ExitPlanModeResult (or extension field) containing values serde_json cannot represent — non-string map keys, f64 NaN/Infinity — reaching exit_plan_handler.handle's return value.","commonSituations":"Custom handler implementations returning structs with HashMap<NonStringKey, _> or NaN floats; SDK version drift introducing a field that breaks the invariant.","solutions":["Inspect the handler's return type for values not representable in JSON (non-string keys, NaN/Infinity)","Change offending fields to JSON-safe types (string keys, Option instead of NaN)","Switch to serde_json::to_value(...)? handling instead of expect if building dynamic results","Report/upgrade if the SDK's own ExitPlanModeResult type triggers this"],"exampleFix":"// before\nlet result = handler.handle(sid, data).await; // contains HashMap<MyEnum, f32 with NaN>\nserde_json::to_value(result).expect(\"ExitPlanModeResult serialization cannot fail\")\n// after\nlet value = serde_json::to_value(handler.handle(sid, data).await)\n    .expect(\"ExitPlanModeResult serialization cannot fail\"); // after making fields JSON-safe","handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":["rust","serde","panic","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"cd8cf15dc3f9e762615790aaed0a771a0f392755","analyzedAt":"2026-09-09T18:32:31.973Z","contentChangedAt":"2026-09-09T18:32:31.973Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}