mirror of
https://github.com/openai/codex.git
synced 2026-05-01 20:02:05 +03:00
Add remote --cd forwarding for app-server sessions (#16700)
Addresses #16124 Problem: `codex --remote --cd <path>` canonicalized the path locally and then omitted it from remote thread lifecycle requests, so remote-only working directories failed or were ignored. Solution: Keep remote startup on the local cwd, forward explicit `--cd` values verbatim to `thread/start`, `thread/resume`, and `thread/fork`, and cover the behavior with `codex-tui` tests. Testing: I manually tested `--remote --cd` with both absolute and relative paths and validated correct behavior. --- Update based on code review feedback: Problem: Remote `--cd` was forwarded to `thread/resume` and `thread/fork`, but not to `thread/list` lookups, so `--resume --last` and picker flows could select a session from the wrong cwd; relative cwd filters also failed against stored absolute paths. Solution: Apply explicit remote `--cd` to `thread/list` lookups for `--last` and picker flows, normalize relative cwd filters on the app-server before exact matching, and document/test the behavior.
This commit is contained in:
@@ -577,15 +577,42 @@ fn latest_session_lookup_params(
|
||||
source_kinds: (!include_non_interactive)
|
||||
.then_some(vec![ThreadSourceKind::Cli, ThreadSourceKind::VsCode]),
|
||||
archived: Some(false),
|
||||
cwd: if is_remote {
|
||||
None
|
||||
} else {
|
||||
cwd_filter.map(|cwd| cwd.to_string_lossy().to_string())
|
||||
},
|
||||
cwd: cwd_filter.map(|cwd| cwd.to_string_lossy().to_string()),
|
||||
search_term: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn config_cwd_for_app_server_target(
|
||||
cwd: Option<&Path>,
|
||||
app_server_target: &AppServerTarget,
|
||||
) -> std::io::Result<AbsolutePathBuf> {
|
||||
if matches!(app_server_target, AppServerTarget::Remote { .. }) {
|
||||
return AbsolutePathBuf::current_dir();
|
||||
}
|
||||
|
||||
match cwd {
|
||||
Some(path) => AbsolutePathBuf::from_absolute_path(path.canonicalize()?),
|
||||
None => AbsolutePathBuf::current_dir(),
|
||||
}
|
||||
}
|
||||
|
||||
fn latest_session_cwd_filter<'a>(
|
||||
remote_mode: bool,
|
||||
remote_cwd_override: Option<&'a Path>,
|
||||
config: &'a Config,
|
||||
show_all: bool,
|
||||
) -> Option<&'a Path> {
|
||||
if show_all {
|
||||
return None;
|
||||
}
|
||||
|
||||
if remote_mode {
|
||||
remote_cwd_override
|
||||
} else {
|
||||
Some(config.cwd.as_path())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_main(
|
||||
mut cli: Cli,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
@@ -604,6 +631,10 @@ pub async fn run_main(
|
||||
auth_token: remote_auth_token.clone(),
|
||||
})
|
||||
.unwrap_or(AppServerTarget::Embedded);
|
||||
let remote_cwd_override = cli
|
||||
.cwd
|
||||
.clone()
|
||||
.filter(|_| matches!(app_server_target, AppServerTarget::Remote { .. }));
|
||||
let (sandbox_mode, approval_policy) = if cli.full_auto {
|
||||
(
|
||||
Some(SandboxMode::WorkspaceWrite),
|
||||
@@ -654,10 +685,7 @@ pub async fn run_main(
|
||||
};
|
||||
|
||||
let cwd = cli.cwd.clone();
|
||||
let config_cwd = match cwd.as_deref() {
|
||||
Some(path) => AbsolutePathBuf::from_absolute_path(path.canonicalize()?)?,
|
||||
None => AbsolutePathBuf::current_dir()?,
|
||||
};
|
||||
let config_cwd = config_cwd_for_app_server_target(cwd.as_deref(), &app_server_target)?;
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
let config_toml = match load_config_as_toml_with_cli_overrides(
|
||||
@@ -745,7 +773,11 @@ pub async fn run_main(
|
||||
model,
|
||||
approval_policy,
|
||||
sandbox_mode,
|
||||
cwd,
|
||||
cwd: if matches!(app_server_target, AppServerTarget::Remote { .. }) {
|
||||
None
|
||||
} else {
|
||||
cwd
|
||||
},
|
||||
model_provider: model_provider_override.clone(),
|
||||
config_profile: cli.config_profile.clone(),
|
||||
codex_self_exe: arg0_paths.codex_self_exe.clone(),
|
||||
@@ -907,6 +939,7 @@ pub async fn run_main(
|
||||
arg0_paths,
|
||||
loader_overrides,
|
||||
app_server_target,
|
||||
remote_cwd_override,
|
||||
config,
|
||||
overrides,
|
||||
cli_kv_overrides,
|
||||
@@ -925,6 +958,7 @@ async fn run_ratatui_app(
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
loader_overrides: LoaderOverrides,
|
||||
app_server_target: AppServerTarget,
|
||||
remote_cwd_override: Option<PathBuf>,
|
||||
initial_config: Config,
|
||||
overrides: ConfigOverrides,
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
@@ -983,18 +1017,21 @@ async fn run_ratatui_app(
|
||||
let needs_onboarding_app_server =
|
||||
should_show_trust_screen_flag || initial_config.model_provider.requires_openai_auth;
|
||||
let mut onboarding_app_server = if needs_onboarding_app_server {
|
||||
Some(AppServerSession::new(
|
||||
start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths.clone(),
|
||||
initial_config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
Some(
|
||||
AppServerSession::new(
|
||||
start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths.clone(),
|
||||
initial_config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
.with_remote_cwd_override(remote_cwd_override.clone()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1097,18 +1134,21 @@ async fn run_ratatui_app(
|
||||
|| cli.resume_picker
|
||||
|| cli.fork_picker;
|
||||
let mut session_lookup_app_server = if needs_app_server_session_lookup {
|
||||
Some(AppServerSession::new(
|
||||
start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths.clone(),
|
||||
config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
Some(
|
||||
AppServerSession::new(
|
||||
start_app_server(
|
||||
&app_server_target,
|
||||
arg0_paths.clone(),
|
||||
config.clone(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides.clone(),
|
||||
cloud_requirements.clone(),
|
||||
feedback.clone(),
|
||||
)
|
||||
.await?,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
.with_remote_cwd_override(remote_cwd_override.clone()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -1127,12 +1167,21 @@ async fn run_ratatui_app(
|
||||
}
|
||||
}
|
||||
} else if cli.fork_last {
|
||||
let filter_cwd = if remote_mode {
|
||||
latest_session_cwd_filter(
|
||||
remote_mode,
|
||||
remote_cwd_override.as_deref(),
|
||||
&config,
|
||||
cli.fork_show_all,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let Some(app_server) = session_lookup_app_server.as_mut() else {
|
||||
unreachable!("session lookup app server should be initialized for --fork --last");
|
||||
};
|
||||
match lookup_latest_session_target_with_app_server(
|
||||
app_server, &config, /*cwd_filter*/ None,
|
||||
/*include_non_interactive*/ false,
|
||||
app_server, &config, filter_cwd, /*include_non_interactive*/ false,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
@@ -1179,11 +1228,12 @@ async fn run_ratatui_app(
|
||||
}
|
||||
}
|
||||
} else if cli.resume_last {
|
||||
let filter_cwd = if cli.resume_show_all {
|
||||
None
|
||||
} else {
|
||||
Some(config.cwd.as_path())
|
||||
};
|
||||
let filter_cwd = latest_session_cwd_filter(
|
||||
remote_mode,
|
||||
remote_cwd_override.as_deref(),
|
||||
&config,
|
||||
cli.resume_show_all,
|
||||
);
|
||||
let Some(app_server) = session_lookup_app_server.as_mut() else {
|
||||
unreachable!("session lookup app server should be initialized for --resume --last");
|
||||
};
|
||||
@@ -1334,7 +1384,7 @@ async fn run_ratatui_app(
|
||||
|
||||
let app_result = App::run(
|
||||
&mut tui,
|
||||
AppServerSession::new(app_server),
|
||||
AppServerSession::new(app_server).with_remote_cwd_override(remote_cwd_override),
|
||||
config,
|
||||
cli_kv_overrides.clone(),
|
||||
overrides.clone(),
|
||||
@@ -1795,12 +1845,9 @@ mod tests {
|
||||
-> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let config = build_config(&temp_dir).await?;
|
||||
let cwd = temp_dir.path().join("project");
|
||||
|
||||
let params = latest_session_lookup_params(
|
||||
/*is_remote*/ true,
|
||||
&config,
|
||||
Some(cwd.as_path()),
|
||||
/*is_remote*/ true, &config, /*cwd_filter*/ None,
|
||||
/*include_non_interactive*/ false,
|
||||
);
|
||||
|
||||
@@ -1809,6 +1856,58 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn latest_session_lookup_params_keep_explicit_cwd_filter_for_remote_sessions()
|
||||
-> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let config = build_config(&temp_dir).await?;
|
||||
let cwd = Path::new("repo/on/server");
|
||||
|
||||
let params = latest_session_lookup_params(
|
||||
/*is_remote*/ true,
|
||||
&config,
|
||||
Some(cwd),
|
||||
/*include_non_interactive*/ false,
|
||||
);
|
||||
|
||||
assert_eq!(params.model_providers, None);
|
||||
assert_eq!(params.cwd.as_deref(), Some("repo/on/server"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_cwd_for_app_server_target_uses_current_dir_for_remote_sessions() -> std::io::Result<()>
|
||||
{
|
||||
let remote_only_cwd = if cfg!(windows) {
|
||||
Path::new(r"C:\definitely\not\local\to\this\test")
|
||||
} else {
|
||||
Path::new("/definitely/not/local/to/this/test")
|
||||
};
|
||||
let target = AppServerTarget::Remote {
|
||||
websocket_url: "ws://127.0.0.1:1234/".to_string(),
|
||||
auth_token: None,
|
||||
};
|
||||
|
||||
let config_cwd = config_cwd_for_app_server_target(Some(remote_only_cwd), &target)?;
|
||||
|
||||
assert_eq!(config_cwd, AbsolutePathBuf::current_dir()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_cwd_for_app_server_target_canonicalizes_embedded_cli_cwd() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
let target = AppServerTarget::Embedded;
|
||||
|
||||
let config_cwd = config_cwd_for_app_server_target(Some(temp_dir.path()), &target)?;
|
||||
|
||||
assert_eq!(
|
||||
config_cwd,
|
||||
AbsolutePathBuf::from_absolute_path(temp_dir.path().canonicalize()?)?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn read_session_cwd_returns_none_without_sqlite_or_rollout_path() -> std::io::Result<()> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
|
||||
Reference in New Issue
Block a user