code-mode: fall back to using in process v8 if we fail to resolve external process (#31899)

## Why

Not every Codex distribution currently includes the
`codex-code-mode-host` companion binary. Enabling the process-host
feature should not make code mode unavailable on those surfaces while
packaging support is being completed.

## What changed

- Fall back to an in-process code-mode session only when spawning the
companion binary returns `io::ErrorKind::NotFound`.
- Keep permission, handshake, timeout, and other host failures visible
instead of silently falling back.
- Store the provider's owned-process/in-process choice as one
enum-backed state so later sessions reuse the fallback decision.
- Preserve the underlying spawn `io::Error` while retaining the host
path in the displayed error.
- Update provider, `CodeModeService`, and end-to-end coverage to verify
successful fallback execution.

## Test plan

- `just test -p codex-code-mode`
- `just test -p codex-core missing_process_host`
This commit is contained in:
Channing Conger
2026-07-09 14:53:33 -07:00
committed by Michael Bolin
parent 9d47eb221d
commit 77c42a202a
5 changed files with 149 additions and 55 deletions
+20 -7
View File
@@ -367,7 +367,9 @@ mod tests {
use crate::tools::context::ToolPayload;
use codex_code_mode::CodeModeToolKind;
use codex_code_mode::ExecuteRequest;
use codex_code_mode::FunctionCallOutputContentItem as CodeModeOutputContentItem;
use codex_code_mode::ProcessOwnedCodeModeSessionProvider;
use codex_code_mode::RuntimeResponse;
use codex_protocol::models::FunctionCallOutputContentItem;
use codex_tools::ToolName;
use serde_json::json;
@@ -426,26 +428,37 @@ mod tests {
}
#[tokio::test]
async fn missing_process_host_is_reported_without_failing_service_creation() {
async fn missing_process_host_falls_back_to_in_process_session() {
let service = CodeModeService::new(Arc::new(
ProcessOwnedCodeModeSessionProvider::with_host_program(
"codex-code-mode-host-does-not-exist".into(),
),
));
let error = service
let response = service
.execute(ExecuteRequest {
tool_call_id: "call-1".to_string(),
enabled_tools: Vec::new(),
source: "text('unreachable')".to_string(),
source: "text('fallback')".to_string(),
yield_time_ms: None,
max_output_tokens: None,
})
.await
.err()
.expect("missing host should reject execution");
.expect("missing host should fall back to an in-process session")
.initial_response()
.await
.expect("read fallback response");
assert!(error.contains("failed to spawn code-mode host"));
service.shutdown().await.expect("shutdown unused service");
assert_eq!(
response,
RuntimeResponse::Result {
cell_id: codex_code_mode::CellId::new("1".to_string()),
content_items: vec![CodeModeOutputContentItem::InputText {
text: "fallback".to_string(),
}],
error_text: None,
}
);
service.shutdown().await.expect("shutdown service");
}
}