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

  1. Read the appended server text, it names the exact rejected field or id
  2. Drop the saved thread id and start a fresh thread via thread/start, as process_weixin_message already does on resume failure
  3. Verify config.model against the models the installed codex exposes
  4. 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

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


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@f2074595a2 (2026-08-23). Data as JSON: /api/errors/809d0a561d7f6c7c. Report an issue: GitHub.