Add opt-in concurrent exec-server request dispatch (#36987)

## Why

Sequential dispatch lets a long-running request block unrelated health checks
and cleanup on the same connection.

## What changed

- Add `--concurrent-requests <COUNT>` for local and remote exec-server
  connections, while retaining sequential dispatch when the option is omitted
  or set to `1`.
- Preserve handshake ordering before enabling concurrent dispatch.
- Reserve separate capacity for status, signal, terminate, and close requests so
  they remain responsive when ordinary request capacity is saturated.
- Drain queued client responses during disconnect and cancel outstanding
  request tasks during connection shutdown.

## Testing

- Cover CLI parsing and concurrency-limit validation.
- Verify default sequential behavior, pipelined handshake ordering, concurrent
  request progress, control-request responsiveness, and disconnect handling.

GitOrigin-RevId: 48e4b092e318204ed635543f01f9ee0e7df095fc
This commit is contained in:
Adam Perry @ OpenAI
2026-08-04 22:28:15 +00:00
committed by copyberry
parent 1d952f027e
commit eeae88d8a6
18 changed files with 836 additions and 99 deletions
@@ -87,7 +87,7 @@ async fn exec_server_replays_only_chatgpt_cloudflare_cookies() -> anyhow::Result
(OsString::from("NO_PROXY"), empty.clone()),
(OsString::from("no_proxy"), empty),
];
let mut server = exec_server_with_env(env).await?;
let mut server = exec_server_with_env(env, &[]).await?;
initialize_exec_server(&mut server).await?;
let first_response = execute_http_request(&mut server, CHATGPT_MCP_URL, "first").await?;
@@ -76,10 +76,13 @@ pub(crate) fn test_codex_helper_paths() -> anyhow::Result<TestCodexHelperPaths>
}
pub(crate) async fn exec_server() -> anyhow::Result<ExecServerHarness> {
exec_server_with_env(std::iter::empty::<(&str, &str)>()).await
exec_server_with_env(std::iter::empty::<(&str, &str)>(), &[]).await
}
pub(crate) async fn exec_server_with_env<I, K, V>(env: I) -> anyhow::Result<ExecServerHarness>
pub(crate) async fn exec_server_with_env<I, K, V>(
env: I,
args: &[&str],
) -> anyhow::Result<ExecServerHarness>
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<std::ffi::OsStr>,
@@ -89,6 +92,7 @@ where
let codex_home = TempDir::new()?;
let mut child = Command::new(&helper_paths.codex_exe);
child.args(["exec-server", "--listen", "ws://127.0.0.1:0"]);
child.args(args);
child.stdin(Stdio::null());
child.stdout(Stdio::piped());
child.stderr(Stdio::inherit());
+20 -5
View File
@@ -9,6 +9,8 @@ use std::time::Duration;
use codex_exec_server::CODEX_ARG0_EXEC_HELPER_ARG1;
use codex_exec_server::CODEX_FS_HELPER_ARG1;
use codex_exec_server::ExecServerRuntimePaths;
use codex_exec_server::ExecServerTelemetry;
use codex_exec_server::RequestDispatchMode;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_sandboxing::landlock::CODEX_LINUX_SANDBOX_ARG0;
@@ -161,10 +163,21 @@ fn maybe_run_exec_server_from_test_binary(guard: Option<&TestBinaryDispatchGuard
eprintln!("expected listen URL");
std::process::exit(1);
};
if args.next().is_some() {
eprintln!("unexpected extra arguments");
std::process::exit(1);
}
let remaining_args = args.collect::<Vec<_>>();
let request_dispatch_mode = match remaining_args.as_slice() {
[] => RequestDispatchMode::Inline,
[flag, value] if flag == "--concurrent-requests" => match value.parse() {
Ok(mode) => mode,
Err(error) => {
eprintln!("invalid concurrent request count: {error}");
std::process::exit(1);
}
},
args => {
eprintln!("unexpected exec-server arguments: {args:?}");
std::process::exit(1);
}
};
let current_exe = match env::current_exe() {
Ok(current_exe) => current_exe,
@@ -209,10 +222,12 @@ fn maybe_run_exec_server_from_test_binary(guard: Option<&TestBinaryDispatchGuard
std::process::exit(1);
}
};
let exit_code = match runtime.block_on(codex_exec_server::run_main(
let exit_code = match runtime.block_on(codex_exec_server::run_main_with_telemetry(
&listen_url,
runtime_paths,
ExecServerTelemetry::default(),
http_client_factory,
request_dispatch_mode,
)) {
Ok(()) => 0,
Err(error) => {
@@ -190,7 +190,7 @@ async fn sandboxed_file_system_helper_finds_bwrap_on_preserved_path() -> Result<
}
let helper_path = std::env::join_paths(path_entries)?;
let server = exec_server_with_env([("PATH", helper_path.as_os_str())]).await?;
let server = exec_server_with_env([("PATH", helper_path.as_os_str())], &[]).await?;
let environment = Environment::create_for_tests(Some(server.websocket_url().to_string()))?;
let file_system = environment.get_filesystem();
let workspace = tmp.path().join("workspace");
+30 -24
View File
@@ -162,18 +162,21 @@ async fn exec_server_http_request_uses_configured_system_proxy() -> anyhow::Resu
let proxy_listener = TcpListener::bind("127.0.0.1:0").await?;
let proxy_url = format!("http://{}", proxy_listener.local_addr()?);
let request_url = "http://exec-server-system-proxy.invalid/delegated?route=system";
let mut server = exec_server_with_env([
(SYSTEM_PROXY_REQUEST_URL_ENV, request_url),
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
("HTTP_PROXY", ""),
("http_proxy", ""),
("HTTPS_PROXY", ""),
("https_proxy", ""),
("ALL_PROXY", ""),
("all_proxy", ""),
("NO_PROXY", ""),
("no_proxy", ""),
])
let mut server = exec_server_with_env(
[
(SYSTEM_PROXY_REQUEST_URL_ENV, request_url),
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
("HTTP_PROXY", ""),
("http_proxy", ""),
("HTTPS_PROXY", ""),
("https_proxy", ""),
("ALL_PROXY", ""),
("all_proxy", ""),
("NO_PROXY", ""),
("no_proxy", ""),
],
&[],
)
.await?;
initialize_exec_server(&mut server).await?;
@@ -218,18 +221,21 @@ async fn exec_server_http_request_normalizes_unicode_hostname() -> anyhow::Resul
let proxy_url = format!("http://{}", proxy_listener.local_addr()?);
let request_url = "http://münich.invalid/mcp?route=unicode";
let normalized_url = "http://xn--mnich-kva.invalid/mcp?route=unicode";
let mut server = exec_server_with_env([
(SYSTEM_PROXY_REQUEST_URL_ENV, normalized_url),
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
("HTTP_PROXY", ""),
("http_proxy", ""),
("HTTPS_PROXY", ""),
("https_proxy", ""),
("ALL_PROXY", ""),
("all_proxy", ""),
("NO_PROXY", ""),
("no_proxy", ""),
])
let mut server = exec_server_with_env(
[
(SYSTEM_PROXY_REQUEST_URL_ENV, normalized_url),
(SYSTEM_PROXY_URL_ENV, proxy_url.as_str()),
("HTTP_PROXY", ""),
("http_proxy", ""),
("HTTPS_PROXY", ""),
("https_proxy", ""),
("ALL_PROXY", ""),
("all_proxy", ""),
("NO_PROXY", ""),
("no_proxy", ""),
],
&[],
)
.await?;
initialize_exec_server(&mut server).await?;
+64 -2
View File
@@ -1,12 +1,13 @@
#![cfg(unix)]
mod common;
use codex_exec_server::InitializeParams;
use codex_exec_server::InitializeResponse;
use codex_exec_server_protocol::JSONRPCError;
use codex_exec_server_protocol::JSONRPCErrorError;
use codex_exec_server_protocol::JSONRPCMessage;
use codex_exec_server_protocol::JSONRPCResponse;
use common::exec_server::exec_server;
use common::exec_server::exec_server_with_env;
use pretty_assertions::assert_eq;
use uuid::Uuid;
@@ -34,3 +35,64 @@ async fn exec_server_accepts_initialize() -> anyhow::Result<()> {
server.shutdown().await?;
Ok(())
}
/// Requests retain their wire-order initialization errors even when later handshake messages are pipelined.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_server_rejects_pipelined_requests_before_initialized() -> anyhow::Result<()> {
let mut server = exec_server_with_env(
std::iter::empty::<(&str, &str)>(),
&["--concurrent-requests", "32"],
)
.await?;
let before_initialize_id = server
.send_request("environment/info", serde_json::json!({}))
.await?;
let initialize_id = server
.send_request(
"initialize",
serde_json::to_value(InitializeParams {
client_name: "exec-server-test".to_string(),
resume_session_id: None,
})?,
)
.await?;
assert_eq!(
server.next_event().await?,
JSONRPCMessage::Error(JSONRPCError {
id: before_initialize_id,
error: JSONRPCErrorError {
code: -32600,
data: None,
message: "client must call initialize before using environment info methods"
.to_string(),
},
})
);
let JSONRPCMessage::Response(JSONRPCResponse { id, .. }) = server.next_event().await? else {
panic!("expected initialize response");
};
assert_eq!(id, initialize_id);
let before_initialized_id = server
.send_request("environment/info", serde_json::json!({}))
.await?;
server
.send_notification("initialized", serde_json::json!({}))
.await?;
assert_eq!(
server.next_event().await?,
JSONRPCMessage::Error(JSONRPCError {
id: before_initialized_id,
error: JSONRPCErrorError {
code: -32600,
data: None,
message: "client must send initialized before using environment info methods"
.to_string(),
},
})
);
server.shutdown().await?;
Ok(())
}
+293 -28
View File
@@ -1,8 +1,10 @@
#![cfg(unix)]
mod common;
use std::collections::HashMap;
use codex_exec_server::EnvironmentInfo;
use codex_exec_server::EnvironmentStatus;
use codex_exec_server::EnvironmentStatusKind;
use codex_exec_server::ExecResponse;
use codex_exec_server::InitializeParams;
use codex_exec_server::InitializeResponse;
@@ -16,11 +18,17 @@ use codex_exec_server_protocol::JSONRPCResponse;
use codex_exec_server_protocol::ProcessSandboxType;
use codex_utils_path_uri::PathUri;
use common::exec_server::exec_server;
use common::exec_server::exec_server_with_env;
use pretty_assertions::assert_eq;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> {
let mut server = exec_server().await?;
let process_argv = if cfg!(windows) {
vec!["cmd.exe", "/D", "/C", "exit 0"]
} else {
vec!["true"]
};
let initialize_id = server
.send_request(
"initialize",
@@ -48,7 +56,7 @@ async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> {
"process/start",
serde_json::json!({
"processId": "proc-1",
"argv": ["true"],
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": {},
"tty": false,
@@ -86,6 +94,20 @@ async fn exec_server_starts_process_over_websocket() -> anyhow::Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Result<()> {
let mut server = exec_server().await?;
let process_argv = if cfg!(windows) {
vec!["cmd.exe", "/D", "/C", "ping -n 601 127.0.0.1 >NUL"]
} else {
vec![
"/bin/sh",
"-c",
"parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done",
]
};
let process_env = if cfg!(windows) {
serde_json::json!({ "PATH": std::env::var("PATH")? })
} else {
serde_json::json!({})
};
let initialize_id = server
.send_request(
"initialize",
@@ -112,9 +134,9 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
"process/start",
serde_json::json!({
"processId": "proc-serial-read",
"argv": ["/bin/sh", "-c", "parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done"],
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": {},
"env": process_env,
"tty": false,
"pipeStdin": false,
"arg0": null
@@ -149,9 +171,9 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
"process/start",
serde_json::json!({
"processId": "proc-serial-queued",
"argv": ["/bin/sh", "-c", "parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done"],
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": {},
"env": process_env,
"tty": false,
"pipeStdin": false,
"arg0": null
@@ -159,18 +181,25 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
)
.await?;
let JSONRPCMessage::Response(JSONRPCResponse { id, .. }) = server.next_event().await? else {
let response = server
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
.await?;
let JSONRPCMessage::Response(JSONRPCResponse { id, .. }) = response else {
panic!("expected the blocked process/read to finish before the queued process/start");
};
assert_eq!(id, read_id);
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = server.next_event().await?
else {
let response = server
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
.await?;
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
panic!("expected the queued environment/info response after process/read");
};
assert_eq!(id, queued_environment_info_id);
let _: EnvironmentInfo = serde_json::from_value(result)?;
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = server.next_event().await?
else {
let response = server
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
.await?;
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
panic!("expected the queued process/start response after process/read");
};
assert_eq!(id, queued_start_id);
@@ -182,6 +211,203 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
}
);
for process_id in ["proc-serial-read", "proc-serial-queued"] {
let terminate_id = server
.send_request(
"process/terminate",
serde_json::json!({ "processId": process_id }),
)
.await?;
server
.wait_for_event(|event| {
matches!(
event,
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &terminate_id
)
})
.await?;
}
server.shutdown().await?;
Ok(())
}
/// A long read cannot block health checks; saturated ordinary work cannot block health checks or cleanup.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_server_keeps_control_requests_live_during_long_reads_and_queued_requests()
-> anyhow::Result<()> {
let mut server = exec_server_with_env(
std::iter::empty::<(&str, &str)>(),
&["--concurrent-requests", "32"],
)
.await?;
let process_argv = if cfg!(windows) {
vec!["cmd.exe", "/D", "/C", "ping -n 601 127.0.0.1 >NUL"]
} else {
vec![
"/bin/sh",
"-c",
"parent=$PPID; while kill -0 \"$parent\" 2>/dev/null; do sleep 1; done",
]
};
let process_env = if cfg!(windows) {
serde_json::json!({ "PATH": std::env::var("PATH")? })
} else {
serde_json::json!({})
};
let initialize_id = server
.send_request(
"initialize",
serde_json::to_value(InitializeParams {
client_name: "exec-server-test".to_string(),
resume_session_id: None,
})?,
)
.await?;
assert!(matches!(
server.next_event().await?,
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == initialize_id
));
server
.send_notification("initialized", serde_json::json!({}))
.await?;
let process_start_id = server
.send_request(
"process/start",
serde_json::json!({
"processId": "proc-capacity",
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": process_env,
"tty": false,
"pipeStdin": false,
"arg0": null
}),
)
.await?;
assert!(matches!(
server.next_event().await?,
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == process_start_id
));
let read_params = serde_json::json!({
"processId": "proc-capacity",
"afterSeq": null,
"maxBytes": null,
"waitMs": 600_000
});
server
.send_request("process/read", read_params.clone())
.await?;
let concurrent_start_id = server
.send_request(
"process/start",
serde_json::json!({
"processId": "proc-concurrent",
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": process_env,
"tty": false,
"pipeStdin": false,
"arg0": null
}),
)
.await?;
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = server.next_event().await?
else {
panic!("expected process/start to finish before the pending process/read");
};
assert_eq!(id, concurrent_start_id);
assert_eq!(
serde_json::from_value::<ExecResponse>(result)?,
ExecResponse {
process_id: ProcessId::from("proc-concurrent"),
sandbox_type: Some(ProcessSandboxType::None),
}
);
for _ in 1..32 {
server
.send_request("process/read", read_params.clone())
.await?;
}
let queued_read_id = server.send_request("process/read", read_params).await?;
let environment_info_id = server
.send_request("environment/info", serde_json::json!({}))
.await?;
let response = server
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
.await?;
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
panic!("expected environment/info response at regular request capacity");
};
assert_eq!(id, environment_info_id);
let _: EnvironmentInfo = serde_json::from_value(result)?;
let environment_status_id = server
.send_request("environment/status", serde_json::json!({}))
.await?;
let response = server
.wait_for_event(|event| matches!(event, JSONRPCMessage::Response(_)))
.await?;
let JSONRPCMessage::Response(JSONRPCResponse { id, result }) = response else {
panic!("expected environment/status response at regular request capacity");
};
assert_eq!(id, environment_status_id);
assert_eq!(
serde_json::from_value::<EnvironmentStatus>(result)?,
EnvironmentStatus {
status: EnvironmentStatusKind::Ready,
}
);
let terminate_id = server
.send_request(
"process/terminate",
serde_json::json!({ "processId": "proc-capacity" }),
)
.await?;
let mut terminate_response = None;
let mut queued_read_completed = false;
while terminate_response.is_none() || !queued_read_completed {
match server.next_event().await? {
JSONRPCMessage::Response(JSONRPCResponse { id, result }) if id == terminate_id => {
terminate_response = Some(serde_json::from_value::<TerminateResponse>(result)?);
}
JSONRPCMessage::Response(JSONRPCResponse { id, result }) if id == queued_read_id => {
let _: ReadResponse = serde_json::from_value(result)?;
queued_read_completed = true;
}
JSONRPCMessage::Error(error) => {
anyhow::bail!("unexpected error while waiting for queued requests: {error:?}");
}
JSONRPCMessage::Request(_)
| JSONRPCMessage::Response(_)
| JSONRPCMessage::Notification(_) => {}
}
}
assert_eq!(
terminate_response,
Some(TerminateResponse { running: true })
);
let terminate_id = server
.send_request(
"process/terminate",
serde_json::json!({ "processId": "proc-concurrent" }),
)
.await?;
server
.wait_for_event(|event| {
matches!(
event,
JSONRPCMessage::Response(JSONRPCResponse { id, .. }) if id == &terminate_id
)
})
.await?;
server.shutdown().await?;
Ok(())
}
@@ -189,6 +415,20 @@ async fn exec_server_runs_ordinary_requests_serially_by_default() -> anyhow::Res
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_server_defaults_omitted_pipe_stdin_to_closed_stdin() -> anyhow::Result<()> {
let mut server = exec_server().await?;
let process_argv = if cfg!(windows) {
vec!["cmd.exe", "/D", "/C", "ping -n 2 127.0.0.1 >NUL"]
} else {
vec![
"/bin/sh",
"-c",
"sleep 0.3; if IFS= read -r line; then printf 'read:%s\\n' \"$line\"; else printf 'eof\\n'; fi",
]
};
let process_env = if cfg!(windows) {
serde_json::json!({ "PATH": std::env::var("PATH")? })
} else {
serde_json::json!({})
};
let initialize_id = server
.send_request(
"initialize",
@@ -216,13 +456,9 @@ async fn exec_server_defaults_omitted_pipe_stdin_to_closed_stdin() -> anyhow::Re
"process/start",
serde_json::json!({
"processId": "proc-default-stdin",
"argv": [
"/bin/sh",
"-c",
"sleep 0.3; if IFS= read -r line; then printf 'read:%s\\n' \"$line\"; else printf 'eof\\n'; fi"
],
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": {},
"env": process_env,
"tty": false,
"arg0": null
}),
@@ -284,6 +520,26 @@ async fn exec_server_defaults_omitted_pipe_stdin_to_closed_stdin() -> anyhow::Re
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
let mut server = exec_server().await?;
let process_argv = if cfg!(windows) {
vec![
"powershell.exe",
"-NoProfile",
"-NonInteractive",
"-Command",
"[Console]::Out.WriteLine('line:' + [Console]::In.ReadLine()); [Console]::Out.WriteLine('line:' + [Console]::In.ReadLine())",
]
} else {
vec![
"/bin/sh",
"-c",
"IFS= read -r first; printf 'line:%s\\n' \"$first\"; IFS= read -r second; printf 'line:%s\\n' \"$second\"",
]
};
let process_env = if cfg!(windows) {
serde_json::to_value(std::env::vars().collect::<HashMap<_, _>>())?
} else {
serde_json::json!({})
};
let initialize_id = server
.send_request(
"initialize",
@@ -311,13 +567,9 @@ async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
"process/start",
serde_json::json!({
"processId": "proc-write-id",
"argv": [
"/bin/sh",
"-c",
"IFS= read -r first; printf 'line:%s\\n' \"$first\"; IFS= read -r second; printf 'line:%s\\n' \"$second\""
],
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": {},
"env": process_env,
"tty": false,
"pipeStdin": true,
"arg0": null
@@ -401,13 +653,16 @@ async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
.flat_map(|chunk| chunk.chunk.into_inner()),
);
after_seq = Some(read_response.next_seq.saturating_sub(1));
if read_response.closed || output.ends_with(b"line:second\n") {
if read_response.closed
|| output.ends_with(b"line:second\n")
|| output.ends_with(b"line:second\r\n")
{
break;
}
}
assert_eq!(
String::from_utf8(output)?,
String::from_utf8(output)?.replace("\r\n", "\n"),
"line:first\nline:second\n".to_string()
);
@@ -418,6 +673,16 @@ async fn exec_server_dedupes_retried_process_write_ids() -> anyhow::Result<()> {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_server_resumes_detached_session_without_killing_processes() -> anyhow::Result<()> {
let mut server = exec_server().await?;
let process_argv = if cfg!(windows) {
vec!["cmd.exe", "/D", "/C", "ping -n 6 127.0.0.1 >NUL"]
} else {
vec!["/bin/sh", "-c", "sleep 5"]
};
let process_env = if cfg!(windows) {
serde_json::json!({ "PATH": std::env::var("PATH")? })
} else {
serde_json::json!({})
};
let initialize_id = server
.send_request(
"initialize",
@@ -449,9 +714,9 @@ async fn exec_server_resumes_detached_session_without_killing_processes() -> any
"process/start",
serde_json::json!({
"processId": "proc-resume",
"argv": ["/bin/sh", "-c", "sleep 5"],
"argv": process_argv,
"cwd": PathUri::from_host_native_path(std::env::current_dir()?)?,
"env": {},
"env": process_env,
"tty": false,
"pipeStdin": false,
"arg0": null