BigPizzaV3/CodexPlusPlus · error · anyhow::Error
Codex turn/start 失败:{error}
Error message
Codex turn/start 失败:{error} What it means
The JSON-RPC response to turn/start carried an error object, and run_turn rethrows its message verbatim after the prefix. The text after the colon is whatever the Codex app-server reported, typically an invalid thread id, an unknown model, or a rejected parameter.
Source
Thrown at crates/codex-plus-core/src/connect/app_server.rs:154
let mut response_received = false;
let mut turn_completed = false;
let mut reply_parts = Vec::new();
let mut model = self.config.model.trim().to_string();
let mut usage = TurnUsage::default();
let deadline = tokio::time::Instant::now() + TURN_TIMEOUT;
while !response_received || !turn_completed {
let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());
if remaining.is_zero() {
bail!("等待 Codex 回复超时");
}
let message = self.read_message(remaining).await?;
if is_server_request(&message) {
self.reject_server_request(&message).await?;
continue;
}
if response_id(&message) == Some(id) {
if let Some(error) = rpc_error(&message) {
bail!("Codex turn/start 失败:{error}");
}
let result = message.get("result").cloned().unwrap_or(Value::Null);
if extract_turn_id(&result).is_none() {
bail!("Codex turn/start 未返回 turn id");
}
if model.is_empty() {
model = extract_model(&result).unwrap_or_default();
}
if let Some(reported_usage) = extract_turn_usage(&result) {
usage = reported_usage;
}
response_received = true;
continue;
}
if model.is_empty() {
model = extract_model(&message).unwrap_or_default();
}View on GitHub (pinned to f2074595a2)
Solutions
- Read the appended server text, it names the exact rejected field or id
- Drop the saved thread id and start a fresh thread via thread/start, as process_weixin_message already does on resume failure
- Verify config.model against the models the installed codex exposes
- Keep codex-plus-core and the codex app-server on matched versions
Example fix
// before
let result = server.run_turn(&thread_id, &text).await?;
// after: expired thread id, start a fresh thread and retry once
let result = match server.run_turn(&thread_id, &text).await {
Ok(result) => result,
Err(error) if error.to_string().contains("turn/start 失败") => {
let fresh = server.prepare_thread(None).await?;
server.run_turn(&fresh, &text).await?
}
Err(error) => return Err(error),
}; Defensive patterns
Strategy: fallback
Try / catch
match server.run_turn(&thread_id, prompt).await {
Ok(r) => r,
Err(e) if e.to_string().contains("turn/start 失败") => {
state.thread_ids.remove(&peer);
let fresh = server.prepare_thread(None).await?;
server.run_turn(&fresh, prompt).await?
}
Err(e) => return Err(e),
} Prevention
- Store thread ids keyed per peer and clear them on any turn/start error
- Validate config.model against the installed codex before use
- Upgrade codex and codex-plus-core together
When it happens
Trigger: run_turn(thread_id, prompt) where thread_id no longer exists on the server (session rolled or deleted); config.model names a model the installed codex does not support; the params shape is rejected by a newer or older app-server.
Common situations: Persisted thread_ids from a previous codex version; model renamed after an upgrade while the profile still lists the old slug; app-server and codex-plus-core version skew.
Related errors
- Codex app-server {method} 失败:{error}
- 等待 Codex 回复超时
- Codex turn/start 未返回 turn id
- {error}
- Codex app-server {method} 请求超时
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@f2074595a2 (2026-08-23).
Data as JSON: /api/errors/809d0a561d7f6c7c.
Report an issue: GitHub.