{"record":{"id":"2a672d687542dee4","repo":"openai/codex","slug":"app-server-user-agent-omitted-version-separator","errorCode":null,"errorMessage":"app-server user-agent omitted version separator","messagePattern":"app-server user-agent omitted version separator","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"codex-rs/app-server-daemon/src/client.rs","lineNumber":151,"sourceCode":"    S: AsyncRead + AsyncWrite + Unpin,\n{\n    loop {\n        let frame = websocket\n            .next()\n            .await\n            .ok_or_else(|| anyhow!(\"app-server closed the control socket\"))??;\n        let Message::Text(payload) = frame else {\n            continue;\n        };\n        return serde_json::from_str::<JSONRPCMessage>(&payload)\n            .context(\"failed to parse app-server JSON-RPC message\");\n    }\n}\n\nfn parse_version_from_user_agent(user_agent: &str) -> Result<String> {\n    let (_originator, rest) = user_agent\n        .split_once('/')\n        .ok_or_else(|| anyhow!(\"app-server user-agent omitted version separator\"))?;\n    let version = rest\n        .split_whitespace()\n        .next()\n        .filter(|version| !version.is_empty())\n        .ok_or_else(|| anyhow!(\"app-server user-agent omitted version\"))?;\n    Ok(version.to_string())\n}\n\n#[cfg(all(test, unix))]\nmod tests {\n    use pretty_assertions::assert_eq;\n\n    use super::parse_version_from_user_agent;\n\n    #[test]\n    fn parses_version_from_codex_user_agent() {\n        assert_eq!(\n            parse_version_from_user_agent(","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/app-server-daemon/src/client.rs#L133-L169","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// custom app-server: send a parseable user agent\n// before\nuser_agent: \"my-server\".into(),\n// after\nuser_agent: \"my-server/1.0.0 (custom build)\".into(),","handlingStrategy":"fallback","validationCode":null,"typeGuard":"fn has_version_separator(user_agent: &str) -> bool {\n    user_agent.contains('/')\n}","tryCatchPattern":"let info = match probe(&path).await {\n    Ok(info) => info,\n    Err(err) if err.to_string().contains(\"user-agent\") => {\n        ProbeInfo { app_server_version: \"unknown\".into() } // degrade, then restart the managed server\n    }\n    Err(err) => return Err(err),\n};","preventionTips":["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."],"tags":["rust","codex","daemon","user-agent","json-rpc","version-mismatch"],"backgroundTag":"user-agent-parsing","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}