github/copilot-sdk · error

ExitPlanModeResult serialization cannot fail

Error message

ExitPlanModeResult serialization cannot fail

What it means

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.

Solutions

  1. Inspect the handler's return type for values not representable in JSON (non-string keys, NaN/Infinity)
  2. Change offending fields to JSON-safe types (string keys, Option instead of NaN)
  3. Switch to serde_json::to_value(...)? handling instead of expect if building dynamic results
  4. Report/upgrade if the SDK's own ExitPlanModeResult type triggers this

Example fix

// before
let result = handler.handle(sid, data).await; // contains HashMap<MyEnum, f32 with NaN>
serde_json::to_value(result).expect("ExitPlanModeResult serialization cannot fail")
// after
let value = serde_json::to_value(handler.handle(sid, data).await)
    .expect("ExitPlanModeResult serialization cannot fail"); // after making fields JSON-safe
Defensive patterns

Strategy: type-guard

When it happens

Trigger: 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.

Common situations: Custom handler implementations returning structs with HashMap<NonStringKey, _> or NaN floats; SDK version drift introducing a field that breaks the invariant.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at rust/src/session.rs:3097

        }

        "exitPlanMode.request" => {
            let params = request
                .params
                .as_ref()
                .cloned()
                .unwrap_or(Value::Object(serde_json::Map::new()));
            let data: ExitPlanModeData = match serde_json::from_value(params) {
                Ok(d) => d,
                Err(e) => {
                    warn!(error = %e, "failed to deserialize exitPlanMode.request params, using defaults");
                    ExitPlanModeData::default()
                }
            };

            let rpc_result = if let Some(exit_plan_handler) = handlers.exit_plan_mode.as_ref() {
                let result = exit_plan_handler.handle(sid, data).await;
                serde_json::to_value(result).expect("ExitPlanModeResult serialization cannot fail")
            } else {
                serde_json::json!({ "approved": true })
            };
            let rpc_response = JsonRpcResponse {
                jsonrpc: "2.0".to_string(),
                id: request.id,
                result: Some(rpc_result),
                error: None,
            };
            let _ = client.send_response(&rpc_response).await;
        }

        "autoModeSwitch.request" => {
            let error_code = request
                .params
                .as_ref()
                .and_then(|p| p.get("errorCode"))
                .and_then(|v| v.as_str())

View on GitHub (pinned to cd8cf15dc3)