BigPizzaV3/CodexPlusPlus · critical
Codex app-server 已关闭
Error message
Codex app-server 已关闭{exit_status_suffix} What it means
The Codex app-server child process closed its stdout stream while the library was reading a JSON-RPC message from it, so the pending read/turn cannot continue. The library marks the connection as no longer running and, when the OS reports the exit status, appends it (e.g. ':exit status: 101') to the message. This is thrown from read_message, the single read path used by both request() and run_turn(), so any turn in flight at process death surfaces this error.
Solutions
- Check the exit-status suffix in the message and run `codex app-server` manually to reproduce the crash and see stderr
- Re-run the connect flow to respawn the app-server process (the flag `running` is set false, so a restart is required)
- Verify the codex binary version matches what CodexPlusPlus expects; upgrade or downgrade codex
- Check system resources (memory pressure, OOM-killer logs in dmesg/journalctl) for SIGKILL of the child
Example fix
// before
// assuming the AppServerConnection is still usable after an error
let reply = conn.request(params).await?;
// after
// detect the closed server and respawn before retrying
match conn.request(params).await {
Err(e) if e.to_string().contains("Codex app-server 已关闭") => {
let mut conn = AppServerConnection::spawn(&codex_path)?;
let reply = conn.request(params).await?;
}
other => other?,
} Defensive patterns
Strategy: try-catch
Validate before calling
// before each turn, confirm the child is still alive
fn server_alive(conn: &AppServerConnection) -> bool { conn.is_running() } Type guard
fn ensure_running(conn: &AppServerConnection) -> anyhow::Result<()> {
if !conn.is_running() { anyhow::bail!("app-server 未运行,请重新启动连接"); }
Ok(())
} Try / catch
match conn.request(params).await {
Err(e) if e.to_string().contains("Codex app-server 已关闭") => respawn_and_retry(),
other => other,
} Prevention
- Check `running`/liveness before issuing each turn
- Monitor child stderr and exit events to diagnose crashes early
- Pin a known-good codex binary version
- Watch for OOM kills if turns are long/large
When it happens
Trigger: Calling request() or run_turn() while the spawned `codex app-server` child process has exited or crashes mid-response: the child terminates (panic, bad flag, OOM kill, manual kill) and tokio's next_line() on stdout returns None.
Common situations: The codex binary panics or segfaults during a long turn; the process is killed by the OS (OOM) or by the user; an incompatible codex version crashes on an unsupported request; the child was never launched successfully and died immediately before the first read.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- archived lookup task failed
- 微信连接已停止
- export markdown task failed
- Remote Control candidate check failed
- session export task failed
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/c613051e68ed9f7d.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/connect/app_server.rs:377
if response_id(&message) != Some(id) {
continue;
}
if let Some(error) = rpc_error(&message) {
bail!("Codex app-server {method} 失败:{error}");
}
return Ok(message.get("result").cloned().unwrap_or(Value::Null));
}
}
async fn read_message(&mut self, timeout: Duration) -> anyhow::Result<Value> {
loop {
let line = tokio::time::timeout(timeout, self.stdout.next_line())
.await
.context("等待 Codex app-server 响应超时")??;
let Some(line) = line else {
self.running = false;
let exit = self.child.try_wait().ok().flatten();
bail!(
"Codex app-server 已关闭{}",
exit.map(|status| format!(":{status}")).unwrap_or_default()
);
};
let line = line.trim();
if line.is_empty() {
continue;
}
match serde_json::from_str(line) {
Ok(message) => return Ok(message),
Err(_) => continue,
}
}
}
async fn reject_server_request(&mut self, message: &Value) -> anyhow::Result<()> {
let Some(id) = message.get("id") else {
return Ok(());View on GitHub (pinned to b1ed92e5e4)