mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Allow disabling the in-process code-mode host fallback (#35266)
## What changed - Allow `features.code_mode_host` to use a configuration table with `disable_in_process_fallback`. When enabled, failure to start the standalone host is returned as tool output instead of falling back to embedded V8. - Preserve the existing fallback behavior by default and continue accepting the boolean feature toggle. - Limit displayed host paths in spawn errors to 512 bytes while retaining the executable-bearing suffix and valid UTF-8 boundaries. ## Testing - Cover boolean and table-based feature configuration, fallback-disabled host failures, and bounded ASCII and UTF-8 error paths. GitOrigin-RevId: ab3d014e79054c2f8beef9a658915f01cca197b2
This commit is contained in:
committed by
copyberry
parent
a453588416
commit
cba0e2701c
@@ -38,6 +38,7 @@ type ShutdownResultReceiver = watch::Receiver<Option<Result<(), String>>>;
|
||||
/// Creates code-mode sessions backed by one lazily spawned process host.
|
||||
pub struct ProcessOwnedCodeModeSessionProvider {
|
||||
state: StdMutex<ProviderState>,
|
||||
allow_in_process_fallback: bool,
|
||||
}
|
||||
|
||||
/// Creates code-mode sessions backed by one shared remote WebSocket connection.
|
||||
@@ -56,9 +57,15 @@ impl ProcessOwnedCodeModeSessionProvider {
|
||||
state: StdMutex::new(ProviderState::OwnedProcess(Arc::new(
|
||||
OwnedCodeModeHost::new(host_program),
|
||||
))),
|
||||
allow_in_process_fallback: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn without_in_process_fallback(mut self) -> Self {
|
||||
self.allow_in_process_fallback = false;
|
||||
self
|
||||
}
|
||||
|
||||
fn process_host(&self) -> Option<Arc<OwnedCodeModeHost>> {
|
||||
match &*self
|
||||
.state
|
||||
@@ -91,7 +98,7 @@ impl CodeModeSessionProvider for ProcessOwnedCodeModeSessionProvider {
|
||||
|
||||
match process_host.connection().await {
|
||||
Ok(_) => {}
|
||||
Err(error) if error.host_program_not_found() => {
|
||||
Err(error) if error.host_program_not_found() && self.allow_in_process_fallback => {
|
||||
*self
|
||||
.state
|
||||
.lock()
|
||||
|
||||
@@ -58,6 +58,10 @@ mod transport;
|
||||
const IPC_CHANNEL_CAPACITY: usize = 128;
|
||||
const HOST_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const MAX_WEBSOCKET_FRAME_BYTES: usize = MAX_FRAME_BYTES + std::mem::size_of::<u32>();
|
||||
// Host spawn errors become model-visible tool output. Bound configured paths
|
||||
// while preserving the executable-bearing suffix needed to diagnose failures.
|
||||
const MAX_DISPLAYED_HOST_PROGRAM_BYTES: usize = 512;
|
||||
const TRUNCATED_HOST_PROGRAM_PREFIX: &str = "...";
|
||||
|
||||
pub(super) enum ConnectionError {
|
||||
Spawn {
|
||||
@@ -82,11 +86,27 @@ impl fmt::Display for ConnectionError {
|
||||
Self::Spawn {
|
||||
host_program,
|
||||
error,
|
||||
} => write!(
|
||||
formatter,
|
||||
"failed to spawn code-mode host {}: {error}",
|
||||
host_program.display()
|
||||
),
|
||||
} => {
|
||||
let host_program = host_program.to_string_lossy();
|
||||
if host_program.len() <= MAX_DISPLAYED_HOST_PROGRAM_BYTES {
|
||||
return write!(
|
||||
formatter,
|
||||
"failed to spawn code-mode host {host_program}: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
let mut suffix_start = host_program.len()
|
||||
- (MAX_DISPLAYED_HOST_PROGRAM_BYTES - TRUNCATED_HOST_PROGRAM_PREFIX.len());
|
||||
while !host_program.is_char_boundary(suffix_start) {
|
||||
suffix_start += 1;
|
||||
}
|
||||
|
||||
write!(
|
||||
formatter,
|
||||
"failed to spawn code-mode host {TRUNCATED_HOST_PROGRAM_PREFIX}{}: {error}",
|
||||
&host_program[suffix_start..]
|
||||
)
|
||||
}
|
||||
Self::Other(message) => formatter.write_str(message),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ use tokio_tungstenite::tungstenite::Message;
|
||||
use super::ProcessOwnedCodeModeSession;
|
||||
use super::ProcessOwnedCodeModeSessionProvider;
|
||||
use super::WebSocketCodeModeSessionProvider;
|
||||
use super::connection::ConnectionError;
|
||||
use super::resolve_host_program;
|
||||
use crate::NoopCodeModeSessionDelegate;
|
||||
|
||||
@@ -93,6 +94,41 @@ fn host_program_falls_back_to_its_name_when_main_executable_is_unknown() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_host_error_limits_the_displayed_path_to_512_bytes() {
|
||||
let executable = "codex-code-mode-host-does-not-exist";
|
||||
let host_program = format!("{}{executable}", "missing-directory/".repeat(/*n*/ 64));
|
||||
let expected_suffix = &host_program[host_program.len() - (512 - "...".len())..];
|
||||
let error = ConnectionError::Spawn {
|
||||
host_program: PathBuf::from(&host_program),
|
||||
error: io::Error::new(io::ErrorKind::NotFound, "host unavailable"),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
error.to_string(),
|
||||
format!("failed to spawn code-mode host ...{expected_suffix}: host unavailable")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_host_error_preserves_utf8_boundaries_when_truncating_the_path() {
|
||||
let executable = "codex-code-mode-host-does-not-exist";
|
||||
let host_program = format!("{}{executable}", "🦀".repeat(/*n*/ 256));
|
||||
let error = ConnectionError::Spawn {
|
||||
host_program: PathBuf::from(host_program),
|
||||
error: io::Error::new(io::ErrorKind::NotFound, "host unavailable"),
|
||||
}
|
||||
.to_string();
|
||||
let displayed_path = error
|
||||
.strip_prefix("failed to spawn code-mode host ")
|
||||
.and_then(|message| message.strip_suffix(": host unavailable"))
|
||||
.expect("missing-host error should contain the displayed host path");
|
||||
|
||||
assert!(displayed_path.starts_with("..."));
|
||||
assert!(displayed_path.ends_with(executable));
|
||||
assert!(displayed_path.len() <= 512);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn provider_falls_back_to_in_process_session_when_host_is_missing() {
|
||||
let provider = ProcessOwnedCodeModeSessionProvider::with_host_program(
|
||||
@@ -273,6 +309,23 @@ async fn websocket_provider_executes_over_shared_connector() {
|
||||
.expect("websocket test host task should succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn provider_returns_missing_host_error_when_in_process_fallback_is_disabled() {
|
||||
let provider = ProcessOwnedCodeModeSessionProvider::with_host_program(
|
||||
"codex-code-mode-host-does-not-exist".into(),
|
||||
)
|
||||
.without_in_process_fallback();
|
||||
|
||||
let error = provider
|
||||
.create_session(Arc::new(NoopCodeModeSessionDelegate))
|
||||
.await
|
||||
.err()
|
||||
.expect("missing host should fail when in-process fallback is disabled");
|
||||
|
||||
assert!(error.contains("failed to spawn code-mode host codex-code-mode-host-does-not-exist"));
|
||||
assert!(provider.process_host().is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn shutdown_before_open_does_not_spawn_the_host() {
|
||||
let session = ProcessOwnedCodeModeSession::new();
|
||||
|
||||
Reference in New Issue
Block a user