{"record":{"id":"bc2f1d4182f6435f","repo":"BigPizzaV3/CodexPlusPlus","slug":"codex-turn-start-turn-id","errorCode":null,"errorMessage":"Codex turn/start 未返回 turn id","messagePattern":"Codex turn/start 未返回 turn id","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/connect/app_server.rs","lineNumber":158,"sourceCode":"        let mut usage = TurnUsage::default();\n        let deadline = tokio::time::Instant::now() + TURN_TIMEOUT;\n        while !response_received || !turn_completed {\n            let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());\n            if remaining.is_zero() {\n                bail!(\"等待 Codex 回复超时\");\n            }\n            let message = self.read_message(remaining).await?;\n            if is_server_request(&message) {\n                self.reject_server_request(&message).await?;\n                continue;\n            }\n            if response_id(&message) == Some(id) {\n                if let Some(error) = rpc_error(&message) {\n                    bail!(\"Codex turn/start 失败：{error}\");\n                }\n                let result = message.get(\"result\").cloned().unwrap_or(Value::Null);\n                if extract_turn_id(&result).is_none() {\n                    bail!(\"Codex turn/start 未返回 turn id\");\n                }\n                if model.is_empty() {\n                    model = extract_model(&result).unwrap_or_default();\n                }\n                if let Some(reported_usage) = extract_turn_usage(&result) {\n                    usage = reported_usage;\n                }\n                response_received = true;\n                continue;\n            }\n\n            if model.is_empty() {\n                model = extract_model(&message).unwrap_or_default();\n            }\n            if let Some(reported_usage) = extract_turn_usage(&message) {\n                usage = reported_usage;\n            }\n","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/f2074595a281bc057525c748175c8eb9805b0673/crates/codex-plus-core/src/connect/app_server.rs#L140-L176","documentation":"turn/start returned a success result, but the result object contained no recognizable turn id (extract_turn_id found nothing). The library treats a turn without an id as unusable because later events cannot be correlated, so it aborts the turn immediately.","triggerScenarios":"A codex app-server version whose turn/start result uses a different field name or shape than extract_turn_id expects; the result is Null or truncated; experimental app-server API changes.","commonSituations":"Version skew after upgrading the codex CLI but not codex-plus-core or vice versa; running an unstable or experimental app-server build.","solutions":["Upgrade codex-plus-core to a release matched to the installed codex app-server","Pin or downgrade the codex CLI to the version this crate was built against","Reproduce manually against codex app-server, inspect the turn/start result JSON, and report or patch extract_turn_id"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if let Err(e) = server.run_turn(&thread_id, prompt).await {\n    if e.to_string().contains(\"未返回 turn id\") {\n        // protocol skew: report codex and crate versions, do not blind-retry\n        return Err(e.context(format!(\"codex={}\", codex_version())));\n    }\n    return Err(e);\n}","preventionTips":["Pin the codex CLI version the crate was built against","Smoke-test one turn after any codex upgrade before enabling the connector","Watch release notes for app-server protocol changes"],"tags":["protocol","codex","turn","response-shape"],"backgroundTag":"unexpected-response-shape","analyzedSha":"f2074595a281bc057525c748175c8eb9805b0673","analyzedAt":"2026-08-23T12:52:24.489Z","contentChangedAt":"2026-08-23T12:52:24.489Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}