openai/codex · error · anyhow::Error
app-server user-agent omitted version separator
Error message
app-server user-agent omitted version separator
What it means
After initialize, the daemon reads InitializeResponse.user_agent and extracts the server version by splitting on the first '/' (expected shape 'codex_app_server/0.42.0 (…)'). A user_agent containing no slash at all fails here. It means whatever answered on the control socket is not a codex app-server of the expected vintage — a foreign or custom JSON-RPC server on that path, or a build whose agent string does not follow the originator/version convention.
Source
Thrown at codex-rs/app-server-daemon/src/client.rs:151
S: AsyncRead + AsyncWrite + Unpin,
{
loop {
let frame = websocket
.next()
.await
.ok_or_else(|| anyhow!("app-server closed the control socket"))??;
let Message::Text(payload) = frame else {
continue;
};
return serde_json::from_str::<JSONRPCMessage>(&payload)
.context("failed to parse app-server JSON-RPC message");
}
}
fn parse_version_from_user_agent(user_agent: &str) -> Result<String> {
let (_originator, rest) = user_agent
.split_once('/')
.ok_or_else(|| anyhow!("app-server user-agent omitted version separator"))?;
let version = rest
.split_whitespace()
.next()
.filter(|version| !version.is_empty())
.ok_or_else(|| anyhow!("app-server user-agent omitted version"))?;
Ok(version.to_string())
}
#[cfg(all(test, unix))]
mod tests {
use pretty_assertions::assert_eq;
use super::parse_version_from_user_agent;
#[test]
fn parses_version_from_codex_user_agent() {
assert_eq!(
parse_version_from_user_agent(View on GitHub (pinned to 339751715c)
Solutions
- Restart the daemon so it spawns its own managed app-server instead of probing whatever currently owns the socket path.
- Verify who owns the socket ('ss -x | grep <path>') and remove or redirect the foreign listener.
- Run daemon and app-server from the same codex release so the user_agent convention holds.
- If you maintain a custom app-server, format user_agent as 'name/version …' so the daemon can parse it.
Example fix
// custom app-server: send a parseable user agent // before user_agent: "my-server".into(), // after user_agent: "my-server/1.0.0 (custom build)".into(),
Defensive patterns
Strategy: fallback
Type guard
fn has_version_separator(user_agent: &str) -> bool {
user_agent.contains('/')
} Try / catch
let info = match probe(&path).await {
Ok(info) => info,
Err(err) if err.to_string().contains("user-agent") => {
ProbeInfo { app_server_version: "unknown".into() } // degrade, then restart the managed server
}
Err(err) => return Err(err),
}; Prevention
- Reserve the daemon's socket path for the daemon; never park other JSON-RPC servers on it.
- Keep user_agent in 'originator/version' form in custom servers.
- Version-check at deploy time instead of relying on probe to accept arbitrary servers.
When it happens
Trigger: probe() (client.rs:59 feeding parse_version_from_user_agent) against a socket served by a non-codex or custom server; an app-server build whose user_agent omits '<name>/<version>'; a protocol change that deserializes user_agent to an unrelated value.
Common situations: Another process bound to the daemon's expected socket path; development forks of app-server that tweak the user_agent string; mixing a new daemon with an old managed binary from a different branch.
Related errors
- app-server user-agent omitted version
- app-server closed the control socket
- pid-managed app-server shutdown is unsupported on this platf
- pid-managed updater shutdown is unsupported on this platform
- failed to read start time for pid-managed app server {pid}
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/2a672d687542dee4.
Report an issue: GitHub.