From b7280fa475d65ac50775ea334e3a560f4ba2039e Mon Sep 17 00:00:00 2001 From: Ruslan Nigmatullin Date: Tue, 24 Mar 2026 15:08:20 -0700 Subject: [PATCH 1/2] app-server: Return codex home in initialize response --- .../schema/json/codex_app_server_protocol.schemas.json | 5 +++++ .../schema/json/v1/InitializeResponse.json | 5 +++++ .../schema/typescript/InitializeResponse.ts | 4 ++++ codex-rs/app-server-protocol/src/protocol/v1.rs | 2 ++ codex-rs/app-server/README.md | 2 +- codex-rs/app-server/src/message_processor.rs | 1 + codex-rs/app-server/tests/suite/v2/initialize.rs | 6 ++++++ 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 104a727f3ce..9a21ee6b7f3 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -2257,6 +2257,10 @@ "InitializeResponse": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "codexHome": { + "description": "Absolute path to the server's Codex home directory.", + "type": "string" + }, "platformFamily": { "description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.", "type": "string" @@ -2270,6 +2274,7 @@ } }, "required": [ + "codexHome", "platformFamily", "platformOs", "userAgent" diff --git a/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json b/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json index ada38a65820..383ef4b2182 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json @@ -1,6 +1,10 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { + "codexHome": { + "description": "Absolute path to the server's Codex home directory.", + "type": "string" + }, "platformFamily": { "description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.", "type": "string" @@ -14,6 +18,7 @@ } }, "required": [ + "codexHome", "platformFamily", "platformOs", "userAgent" diff --git a/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts index 47978fc8d15..b0668a5299e 100644 --- a/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts @@ -3,6 +3,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type InitializeResponse = { userAgent: string, +/** + * Absolute path to the server's Codex home directory. + */ +codexHome: string, /** * Platform family for the running app-server target, for example * `"unix"` or `"windows"`. diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index 81f3cc58a80..db6a8d40dff 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -56,6 +56,8 @@ pub struct InitializeCapabilities { #[serde(rename_all = "camelCase")] pub struct InitializeResponse { pub user_agent: String, + /// Absolute path to the server's Codex home directory. + pub codex_home: PathBuf, /// Platform family for the running app-server target, for example /// `"unix"` or `"windows"`. pub platform_family: String, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index c238cc73e36..16036262c4a 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -75,7 +75,7 @@ Use the thread APIs to create, list, or archive conversations. Drive a conversat ## Initialization -Clients must send a single `initialize` request per transport connection before invoking any other method on that connection, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services plus `platformFamily` and `platformOs` strings describing the app-server runtime target; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls on the same connection receive an `"Already initialized"` error. +Clients must send a single `initialize` request per transport connection before invoking any other method on that connection, then acknowledge with an `initialized` notification. The server returns the user agent string it will present to upstream services, `codexHome` for the server's Codex home directory, and `platformFamily` and `platformOs` strings describing the app-server runtime target; subsequent requests issued before initialization receive a `"Not initialized"` error, and repeated `initialize` calls on the same connection receive an `"Already initialized"` error. `initialize.params.capabilities` also supports per-connection notification opt-out via `optOutNotificationMethods`, which is a list of exact method names to suppress for that connection. Matching is exact (no wildcards/prefixes). Unknown method names are accepted and ignored. diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 4f53bfc1a49..3d1ff484b2a 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -581,6 +581,7 @@ impl MessageProcessor { let user_agent = get_codex_user_agent(); let response = InitializeResponse { user_agent, + codex_home: self.config.codex_home.clone(), platform_family: std::env::consts::FAMILY.to_string(), platform_os: std::env::consts::OS.to_string(), }; diff --git a/codex-rs/app-server/tests/suite/v2/initialize.rs b/codex-rs/app-server/tests/suite/v2/initialize.rs index 9b5f0cacc8b..96e101873e9 100644 --- a/codex-rs/app-server/tests/suite/v2/initialize.rs +++ b/codex-rs/app-server/tests/suite/v2/initialize.rs @@ -30,6 +30,7 @@ async fn initialize_uses_client_info_name_as_originator() -> Result<()> { let responses = Vec::new(); let server = create_mock_responses_server_sequence_unchecked(responses).await; let codex_home = TempDir::new()?; + let expected_codex_home = codex_home.path().canonicalize()?; create_config_toml(codex_home.path(), &server.uri(), "never")?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -48,11 +49,13 @@ async fn initialize_uses_client_info_name_as_originator() -> Result<()> { }; let InitializeResponse { user_agent, + codex_home: response_codex_home, platform_family, platform_os, } = to_response::(response)?; assert!(user_agent.starts_with("codex_vscode/")); + assert_eq!(response_codex_home, expected_codex_home); assert_eq!(platform_family, std::env::consts::FAMILY); assert_eq!(platform_os, std::env::consts::OS); Ok(()) @@ -63,6 +66,7 @@ async fn initialize_respects_originator_override_env_var() -> Result<()> { let responses = Vec::new(); let server = create_mock_responses_server_sequence_unchecked(responses).await; let codex_home = TempDir::new()?; + let expected_codex_home = codex_home.path().canonicalize()?; create_config_toml(codex_home.path(), &server.uri(), "never")?; let mut mcp = McpProcess::new_with_env( codex_home.path(), @@ -88,11 +92,13 @@ async fn initialize_respects_originator_override_env_var() -> Result<()> { }; let InitializeResponse { user_agent, + codex_home: response_codex_home, platform_family, platform_os, } = to_response::(response)?; assert!(user_agent.starts_with("codex_originator_via_env_var/")); + assert_eq!(response_codex_home, expected_codex_home); assert_eq!(platform_family, std::env::consts::FAMILY); assert_eq!(platform_os, std::env::consts::OS); Ok(()) From 5c529a3acb47eecf1ddcee80297b9bba0cd980da Mon Sep 17 00:00:00 2001 From: Ruslan Nigmatullin Date: Tue, 24 Mar 2026 15:35:48 -0700 Subject: [PATCH 2/2] cr --- .../json/codex_app_server_protocol.schemas.json | 12 ++++++++++-- .../schema/json/v1/InitializeResponse.json | 14 ++++++++++++-- .../schema/typescript/InitializeResponse.ts | 5 +++-- codex-rs/app-server-protocol/src/protocol/v1.rs | 4 ++-- codex-rs/app-server/src/message_processor.rs | 15 ++++++++++++++- codex-rs/app-server/tests/suite/v2/initialize.rs | 5 +++-- 6 files changed, 44 insertions(+), 11 deletions(-) diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 9a21ee6b7f3..7b2f719fa06 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -1,6 +1,10 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + }, "AdditionalFileSystemPermissions": { "properties": { "read": { @@ -2258,8 +2262,12 @@ "$schema": "http://json-schema.org/draft-07/schema#", "properties": { "codexHome": { - "description": "Absolute path to the server's Codex home directory.", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/v2/AbsolutePathBuf" + } + ], + "description": "Absolute path to the server's $CODEX_HOME directory." }, "platformFamily": { "description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.", diff --git a/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json b/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json index 383ef4b2182..1de65f82f4d 100644 --- a/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v1/InitializeResponse.json @@ -1,9 +1,19 @@ { "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AbsolutePathBuf": { + "description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.", + "type": "string" + } + }, "properties": { "codexHome": { - "description": "Absolute path to the server's Codex home directory.", - "type": "string" + "allOf": [ + { + "$ref": "#/definitions/AbsolutePathBuf" + } + ], + "description": "Absolute path to the server's $CODEX_HOME directory." }, "platformFamily": { "description": "Platform family for the running app-server target, for example `\"unix\"` or `\"windows\"`.", diff --git a/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts b/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts index b0668a5299e..c1d0a822f24 100644 --- a/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts +++ b/codex-rs/app-server-protocol/schema/typescript/InitializeResponse.ts @@ -1,12 +1,13 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { AbsolutePathBuf } from "./AbsolutePathBuf"; export type InitializeResponse = { userAgent: string, /** - * Absolute path to the server's Codex home directory. + * Absolute path to the server's $CODEX_HOME directory. */ -codexHome: string, +codexHome: AbsolutePathBuf, /** * Platform family for the running app-server target, for example * `"unix"` or `"windows"`. diff --git a/codex-rs/app-server-protocol/src/protocol/v1.rs b/codex-rs/app-server-protocol/src/protocol/v1.rs index db6a8d40dff..878d7d47dd2 100644 --- a/codex-rs/app-server-protocol/src/protocol/v1.rs +++ b/codex-rs/app-server-protocol/src/protocol/v1.rs @@ -56,8 +56,8 @@ pub struct InitializeCapabilities { #[serde(rename_all = "camelCase")] pub struct InitializeResponse { pub user_agent: String, - /// Absolute path to the server's Codex home directory. - pub codex_home: PathBuf, + /// Absolute path to the server's $CODEX_HOME directory. + pub codex_home: AbsolutePathBuf, /// Platform family for the running app-server target, for example /// `"unix"` or `"windows"`. pub platform_family: String, diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 3d1ff484b2a..219eda3c276 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -8,6 +8,7 @@ use std::sync::atomic::Ordering; use crate::codex_message_processor::CodexMessageProcessor; use crate::codex_message_processor::CodexMessageProcessorArgs; use crate::config_api::ConfigApi; +use crate::error_code::INTERNAL_ERROR_CODE; use crate::error_code::INVALID_REQUEST_ERROR_CODE; use crate::external_agent_config_api::ExternalAgentConfigApi; use crate::fs_api::FsApi; @@ -579,9 +580,21 @@ impl MessageProcessor { } let user_agent = get_codex_user_agent(); + let codex_home = match self.config.codex_home.clone().try_into() { + Ok(codex_home) => codex_home, + Err(err) => { + let error = JSONRPCErrorError { + code: INTERNAL_ERROR_CODE, + message: format!("Invalid CODEX_HOME: {err}"), + data: None, + }; + self.outgoing.send_error(connection_request_id, error).await; + return; + } + }; let response = InitializeResponse { user_agent, - codex_home: self.config.codex_home.clone(), + codex_home, platform_family: std::env::consts::FAMILY.to_string(), platform_os: std::env::consts::OS.to_string(), }; diff --git a/codex-rs/app-server/tests/suite/v2/initialize.rs b/codex-rs/app-server/tests/suite/v2/initialize.rs index 96e101873e9..165160468f7 100644 --- a/codex-rs/app-server/tests/suite/v2/initialize.rs +++ b/codex-rs/app-server/tests/suite/v2/initialize.rs @@ -14,6 +14,7 @@ use codex_app_server_protocol::ThreadStartResponse; use codex_app_server_protocol::TurnStartParams; use codex_app_server_protocol::TurnStartResponse; use codex_app_server_protocol::UserInput as V2UserInput; +use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_cargo_bin::cargo_bin; use core_test_support::fs_wait; use pretty_assertions::assert_eq; @@ -30,7 +31,7 @@ async fn initialize_uses_client_info_name_as_originator() -> Result<()> { let responses = Vec::new(); let server = create_mock_responses_server_sequence_unchecked(responses).await; let codex_home = TempDir::new()?; - let expected_codex_home = codex_home.path().canonicalize()?; + let expected_codex_home = AbsolutePathBuf::try_from(codex_home.path().canonicalize()?)?; create_config_toml(codex_home.path(), &server.uri(), "never")?; let mut mcp = McpProcess::new(codex_home.path()).await?; @@ -66,7 +67,7 @@ async fn initialize_respects_originator_override_env_var() -> Result<()> { let responses = Vec::new(); let server = create_mock_responses_server_sequence_unchecked(responses).await; let codex_home = TempDir::new()?; - let expected_codex_home = codex_home.path().canonicalize()?; + let expected_codex_home = AbsolutePathBuf::try_from(codex_home.path().canonicalize()?)?; create_config_toml(codex_home.path(), &server.uri(), "never")?; let mut mcp = McpProcess::new_with_env( codex_home.path(),