Fix: TUI starting in wrong CWD (#23538)

This fixes a regression wher codex could start in the wrong directory
when a live local app-server socket was present. The issue was that
implicit local socket reuse was being treated like an explicit remote
workspace session, which dropped the invoking cwd unless --cd was
passed.

The change separates local socket transport from true remote workspace
semantics.
- Plain local startup keeps local cwd, trust, resume, picker, and
config-refresh behavior.
- Explicit --remote keeps the existing remote cwd behavior.
- Added coverage for launch target selection and local-session
filtering/cwd behavior.

Steps to test:
- Start a local app-server from a different directory than the repo you
want to use.
  - Launch codex from a project/worktree without --cd.
- Confirm the session starts in the invoking directory, not the
app-server process directory.
- Confirm explicit codex --remote ... still preserves existing remote
behavior.
This commit is contained in:
canvrno-oai
2026-05-19 15:48:40 -07:00
committed by GitHub
Unverified
parent d86352d520
commit 27c4c67b15
8 changed files with 343 additions and 91 deletions
+4 -4
View File
@@ -3,6 +3,7 @@
//! This module owns the `App` struct, shared imports, and the high-level run loop that coordinates
//! the focused app submodules.
use crate::AppServerTarget;
use crate::app_backtrack::BacktrackState;
use crate::app_command::AppCommand;
use crate::app_event::AppEvent;
@@ -82,7 +83,6 @@ use crate::workspace_command::AppServerWorkspaceCommandRunner;
use crate::workspace_command::WorkspaceCommandRunner;
use codex_ansi_escape::ansi_escape_line;
use codex_app_server_client::AppServerRequestHandle;
use codex_app_server_client::RemoteAppServerEndpoint;
use codex_app_server_client::TypedRequestError;
use codex_app_server_protocol::AddCreditsNudgeCreditType;
use codex_app_server_protocol::AskForApproval;
@@ -516,7 +516,7 @@ pub(crate) struct App {
pub(crate) feedback: codex_feedback::CodexFeedback,
feedback_audience: FeedbackAudience,
environment_manager: Arc<EnvironmentManager>,
remote_app_server_endpoint: Option<RemoteAppServerEndpoint>,
app_server_target: AppServerTarget,
/// Set when the user confirms an update; propagated on exit.
pub(crate) pending_update_action: Option<UpdateAction>,
@@ -654,7 +654,7 @@ impl App {
is_first_run: bool,
entered_trust_nux: bool,
should_prompt_windows_sandbox_nux_at_startup: bool,
remote_app_server_endpoint: Option<RemoteAppServerEndpoint>,
app_server_target: AppServerTarget,
state_db: Option<StateDbHandle>,
environment_manager: Arc<EnvironmentManager>,
startup_hooks_browser: Option<HooksListEntry>,
@@ -941,7 +941,7 @@ See the Codex keymap documentation for supported actions and examples."
feedback: feedback.clone(),
feedback_audience,
environment_manager,
remote_app_server_endpoint,
app_server_target,
pending_update_action: None,
pending_shutdown_exit_thread_id: None,
windows_sandbox: WindowsSandboxState::default(),
+3 -6
View File
@@ -57,10 +57,7 @@ impl App {
AppEvent::OpenResumePicker => {
let picker_app_server = match crate::start_app_server_for_picker(
&self.config,
&match self.remote_app_server_endpoint.clone() {
Some(endpoint) => crate::AppServerTarget::Remote { endpoint },
None => crate::AppServerTarget::Embedded,
},
&self.app_server_target,
self.state_db.clone(),
self.environment_manager.clone(),
)
@@ -1680,7 +1677,7 @@ impl App {
{
Ok(()) => {
self.chat_widget.update_skill_enabled(path, enabled);
if !app_server.is_remote()
if !app_server.uses_remote_workspace()
&& let Err(err) = self.refresh_in_memory_config_from_disk().await
{
tracing::warn!(
@@ -1724,7 +1721,7 @@ impl App {
{
Ok(_) => {
self.chat_widget.update_connector_enabled(&id, enabled);
if !app_server.is_remote()
if !app_server.uses_remote_workspace()
&& let Err(err) = self.refresh_in_memory_config_from_disk().await
{
tracing::warn!(error = %err, "failed to refresh config after app toggle");
+1 -1
View File
@@ -633,7 +633,7 @@ impl App {
}
let current_cwd = self.config.cwd.to_path_buf();
let resume_cwd = if self.remote_app_server_endpoint.is_some() {
let resume_cwd = if self.app_server_target.uses_remote_workspace() {
current_cwd.clone()
} else {
match crate::session_resume::resolve_cwd_for_resume_or_fork(
+1 -1
View File
@@ -45,7 +45,7 @@ pub(super) async fn make_test_app() -> App {
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
remote_app_server_endpoint: None,
app_server_target: crate::AppServerTarget::Embedded,
pending_update_action: None,
pending_shutdown_exit_thread_id: None,
windows_sandbox: WindowsSandboxState::default(),
+2 -2
View File
@@ -3867,7 +3867,7 @@ async fn make_test_app() -> App {
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
remote_app_server_endpoint: None,
app_server_target: crate::AppServerTarget::Embedded,
pending_update_action: None,
pending_shutdown_exit_thread_id: None,
windows_sandbox: WindowsSandboxState::default(),
@@ -3930,7 +3930,7 @@ async fn make_test_app_with_channels() -> (
feedback: codex_feedback::CodexFeedback::new(),
feedback_audience: FeedbackAudience::External,
environment_manager: Arc::new(EnvironmentManager::default_for_tests()),
remote_app_server_endpoint: None,
app_server_target: crate::AppServerTarget::Embedded,
pending_update_action: None,
pending_shutdown_exit_thread_id: None,
windows_sandbox: WindowsSandboxState::default(),
+8 -9
View File
@@ -149,10 +149,11 @@ pub(crate) struct AppServerSession {
client: AppServerClient,
next_request_id: i64,
remote_cwd_override: Option<PathBuf>,
thread_params_mode: ThreadParamsMode,
}
#[derive(Clone, Copy)]
enum ThreadParamsMode {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ThreadParamsMode {
Embedded,
Remote,
}
@@ -182,11 +183,12 @@ pub(crate) enum TurnPermissionsOverride {
}
impl AppServerSession {
pub(crate) fn new(client: AppServerClient) -> Self {
pub(crate) fn new(client: AppServerClient, thread_params_mode: ThreadParamsMode) -> Self {
Self {
client,
next_request_id: 1,
remote_cwd_override: None,
thread_params_mode,
}
}
@@ -199,8 +201,8 @@ impl AppServerSession {
self.remote_cwd_override.as_deref()
}
pub(crate) fn is_remote(&self) -> bool {
matches!(self.client, AppServerClient::Remote(_))
pub(crate) fn uses_remote_workspace(&self) -> bool {
matches!(self.thread_params_mode, ThreadParamsMode::Remote)
}
pub(crate) async fn bootstrap(&mut self, config: &Config) -> Result<AppServerBootstrap> {
@@ -426,10 +428,7 @@ impl AppServerSession {
}
fn thread_params_mode(&self) -> ThreadParamsMode {
match &self.client {
AppServerClient::InProcess(_) => ThreadParamsMode::Embedded,
AppServerClient::Remote(_) => ThreadParamsMode::Remote,
}
self.thread_params_mode
}
async fn fork_parent_title_from_app_server(
+300 -52
View File
@@ -20,6 +20,7 @@ use app::App;
pub use app::AppExitInfo;
pub use app::ExitReason;
use app_server_session::AppServerSession;
use app_server_session::ThreadParamsMode;
use codex_app_server_client::AppServerClient;
use codex_app_server_client::DEFAULT_IN_PROCESS_CHANNEL_CAPACITY;
use codex_app_server_client::InProcessAppServerClient;
@@ -312,9 +313,24 @@ async fn start_embedded_app_server(
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum AppServerTarget {
Embedded,
LocalDaemon { endpoint: RemoteAppServerEndpoint },
Remote { endpoint: RemoteAppServerEndpoint },
}
impl AppServerTarget {
pub(crate) fn uses_remote_workspace(&self) -> bool {
matches!(self, Self::Remote { .. })
}
fn thread_params_mode(&self) -> ThreadParamsMode {
if self.uses_remote_workspace() {
ThreadParamsMode::Remote
} else {
ThreadParamsMode::Embedded
}
}
}
async fn init_state_db_for_app_server_target(
config: &Config,
app_server_target: &AppServerTarget,
@@ -326,7 +342,9 @@ async fn init_state_db_for_app_server_target(
err.to_string(),
))
}),
AppServerTarget::Remote { .. } => Ok(state_db::get_state_db(config).await),
AppServerTarget::LocalDaemon { .. } | AppServerTarget::Remote { .. } => {
Ok(state_db::get_state_db(config).await)
}
}
}
@@ -496,7 +514,9 @@ async fn start_app_server(
)
.await
.map(AppServerClient::InProcess),
AppServerTarget::Remote { endpoint } => connect_remote_app_server(endpoint.clone()).await,
AppServerTarget::LocalDaemon { endpoint } | AppServerTarget::Remote { endpoint } => {
connect_remote_app_server(endpoint.clone()).await
}
}
}
@@ -520,7 +540,10 @@ pub(crate) async fn start_app_server_for_picker(
environment_manager,
)
.await?;
Ok(AppServerSession::new(app_server))
Ok(AppServerSession::new(
app_server,
target.thread_params_mode(),
))
}
#[cfg(test)]
@@ -695,7 +718,7 @@ async fn lookup_latest_session_target_with_app_server(
) -> color_eyre::Result<Option<resume_picker::SessionTarget>> {
let response = app_server
.thread_list(latest_session_lookup_params(
app_server.is_remote(),
app_server.uses_remote_workspace(),
config,
cwd_filter,
include_non_interactive,
@@ -708,7 +731,7 @@ async fn lookup_latest_session_target_with_app_server(
}
fn latest_session_lookup_params(
is_remote: bool,
uses_remote_workspace: bool,
config: &Config,
cwd_filter: Option<&Path>,
include_non_interactive: bool,
@@ -718,7 +741,7 @@ fn latest_session_lookup_params(
limit: Some(1),
sort_key: Some(AppServerThreadSortKey::UpdatedAt),
sort_direction: None,
model_providers: if is_remote {
model_providers: if uses_remote_workspace {
None
} else {
Some(vec![config.model_provider_id.clone()])
@@ -737,7 +760,7 @@ fn config_cwd_for_app_server_target(
app_server_target: &AppServerTarget,
environment_manager: &EnvironmentManager,
) -> std::io::Result<Option<AbsolutePathBuf>> {
if matches!(app_server_target, AppServerTarget::Remote { .. })
if app_server_target.uses_remote_workspace()
|| environment_manager
.default_environment()
.is_some_and(|environment| environment.is_remote())
@@ -758,12 +781,11 @@ fn should_load_configured_environments(
loader_overrides: &LoaderOverrides,
app_server_target: &AppServerTarget,
) -> bool {
!loader_overrides.ignore_user_config
&& !matches!(app_server_target, AppServerTarget::Remote { .. })
!loader_overrides.ignore_user_config && !app_server_target.uses_remote_workspace()
}
fn latest_session_cwd_filter<'a>(
remote_mode: bool,
uses_remote_workspace: bool,
remote_cwd_override: Option<&'a Path>,
config: &'a Config,
show_all: bool,
@@ -772,13 +794,62 @@ fn latest_session_cwd_filter<'a>(
return None;
}
if remote_mode {
if uses_remote_workspace {
remote_cwd_override
} else {
Some(config.cwd.as_path())
}
}
fn app_server_target_for_launch(
explicit_remote_endpoint: Option<RemoteAppServerEndpoint>,
default_daemon_socket: Option<AbsolutePathBuf>,
can_reuse_implicit_local_daemon: bool,
) -> AppServerTarget {
match explicit_remote_endpoint {
Some(endpoint) => AppServerTarget::Remote { endpoint },
None if can_reuse_implicit_local_daemon => {
default_daemon_socket.map_or(AppServerTarget::Embedded, |socket_path| {
AppServerTarget::LocalDaemon {
endpoint: RemoteAppServerEndpoint::UnixSocket { socket_path },
}
})
}
None => AppServerTarget::Embedded,
}
}
fn loader_overrides_are_default(loader_overrides: &LoaderOverrides) -> bool {
let loader_overrides_are_default = loader_overrides.user_config_path.is_none()
&& loader_overrides.user_config_profile.is_none()
&& loader_overrides.managed_config_path.is_none()
&& loader_overrides.system_config_path.is_none()
&& loader_overrides.system_requirements_path.is_none()
&& !loader_overrides.ignore_managed_requirements
&& !loader_overrides.ignore_user_config
&& !loader_overrides.ignore_user_and_project_exec_policy_rules
&& loader_overrides
.macos_managed_config_requirements_base64
.is_none();
#[cfg(target_os = "macos")]
let loader_overrides_are_default =
loader_overrides_are_default && loader_overrides.managed_preferences_base64.is_none();
loader_overrides_are_default
}
fn can_reuse_implicit_local_daemon(
cli_kv_overrides: &[(String, toml::Value)],
loader_overrides: &LoaderOverrides,
strict_config: bool,
has_non_replayable_launch_overrides: bool,
) -> bool {
// A reused daemon cannot adopt this invocation's full launch config state.
cli_kv_overrides.is_empty()
&& loader_overrides_are_default(loader_overrides)
&& !strict_config
&& !has_non_replayable_launch_overrides
}
pub async fn run_main(
mut cli: Cli,
arg0_paths: Arg0DispatchPaths,
@@ -830,21 +901,32 @@ pub async fn run_main(
}
};
let remote_endpoint = match explicit_remote_endpoint {
Some(endpoint) => Some(endpoint),
None => maybe_probe_default_daemon_socket(&codex_home)
.await
.map(|socket_path| RemoteAppServerEndpoint::UnixSocket { socket_path }),
let mut launch_loader_overrides = loader_overrides.clone();
if let Some(profile_v2) = cli.config_profile_v2.as_ref() {
let user_config_path = resolve_profile_v2_config_path(&codex_home, profile_v2);
launch_loader_overrides.user_config_path = Some(user_config_path);
launch_loader_overrides.user_config_profile = Some(profile_v2.clone());
}
let reuse_implicit_local_daemon = can_reuse_implicit_local_daemon(
&cli_kv_overrides,
&launch_loader_overrides,
strict_config,
cli.bypass_hook_trust,
);
let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon {
maybe_probe_default_daemon_socket(&codex_home).await
} else {
None
};
let app_server_target = remote_endpoint
.clone()
.map_or(AppServerTarget::Embedded, |endpoint| {
AppServerTarget::Remote { endpoint }
});
let app_server_target = app_server_target_for_launch(
explicit_remote_endpoint,
default_daemon,
reuse_implicit_local_daemon,
);
let remote_cwd_override = cli
.cwd
.clone()
.filter(|_| matches!(app_server_target, AppServerTarget::Remote { .. }));
.filter(|_| app_server_target.uses_remote_workspace());
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
arg0_paths.codex_self_exe.clone(),
@@ -952,7 +1034,7 @@ pub async fn run_main(
model,
approval_policy,
sandbox_mode,
cwd: if matches!(app_server_target, AppServerTarget::Remote { .. }) {
cwd: if app_server_target.uses_remote_workspace() {
None
} else {
cwd
@@ -1072,7 +1154,7 @@ pub async fn run_main(
}
}
if matches!(app_server_target, AppServerTarget::Embedded) {
if !app_server_target.uses_remote_workspace() {
#[allow(clippy::print_stderr)]
if let Err(err) = enforce_login_restrictions(&AuthConfig {
codex_home: config.codex_home.to_path_buf(),
@@ -1180,7 +1262,6 @@ pub async fn run_main(
feedback,
log_db,
state_db,
remote_endpoint,
environment_manager,
)
.await
@@ -1202,10 +1283,9 @@ async fn run_ratatui_app(
feedback: codex_feedback::CodexFeedback,
log_db: Option<log_db::LogDbLayer>,
state_db: Option<StateDbHandle>,
remote_endpoint: Option<RemoteAppServerEndpoint>,
environment_manager: Arc<EnvironmentManager>,
) -> color_eyre::Result<AppExitInfo> {
let remote_mode = matches!(&app_server_target, AppServerTarget::Remote { .. });
let uses_remote_workspace = app_server_target.uses_remote_workspace();
color_eyre::install()?;
tooltips::announcement::prewarm();
@@ -1268,7 +1348,7 @@ async fn run_ratatui_app(
)
.await
{
Ok(app_server) => AppServerSession::new(app_server),
Ok(app_server) => AppServerSession::new(app_server, app_server_target.thread_params_mode()),
Err(err) => {
terminal_restore_guard.restore_silently();
session_log::log_session_end();
@@ -1278,7 +1358,8 @@ async fn run_ratatui_app(
.with_remote_cwd_override(remote_cwd_override.clone());
let mut app_server = Some(app_server_session);
let should_show_trust_screen_flag = !remote_mode && should_show_trust_screen(&initial_config);
let should_show_trust_screen_flag =
!uses_remote_workspace && should_show_trust_screen(&initial_config);
let mut trust_decision_was_made = false;
let login_status = if initial_config.model_provider.requires_openai_auth {
let Some(app_server) = app_server.as_mut() else {
@@ -1328,7 +1409,7 @@ async fn run_ratatui_app(
// If this onboarding run included the login step, always refresh cloud requirements and
// rebuild config. This avoids missing newly available cloud requirements due to login
// status detection edge cases.
if show_login_screen && !remote_mode {
if show_login_screen && !uses_remote_workspace {
cloud_requirements = cloud_requirements_loader_for_storage(
initial_config.codex_home.to_path_buf(),
/*enable_codex_api_key_env*/ false,
@@ -1341,7 +1422,7 @@ async fn run_ratatui_app(
// If the user made an explicit trust decision, or we showed the login flow, reload config
// so current process state reflects persisted trust/auth changes.
if onboarding_result.directory_trust_decision.is_some()
|| (show_login_screen && !remote_mode)
|| (show_login_screen && !uses_remote_workspace)
{
load_config_or_exit(
cli_kv_overrides.clone(),
@@ -1389,7 +1470,7 @@ async fn run_ratatui_app(
}
} else if cli.fork_last {
let filter_cwd = latest_session_cwd_filter(
remote_mode,
uses_remote_workspace,
remote_cwd_override.as_deref(),
&config,
cli.fork_show_all,
@@ -1446,7 +1527,7 @@ async fn run_ratatui_app(
}
} else if cli.resume_last {
let filter_cwd = latest_session_cwd_filter(
remote_mode,
uses_remote_workspace,
remote_cwd_override.as_deref(),
&config,
cli.resume_show_all,
@@ -1496,7 +1577,7 @@ async fn run_ratatui_app(
};
let current_cwd = config.cwd.clone();
let allow_prompt = !remote_mode && cli.cwd.is_none();
let allow_prompt = !uses_remote_workspace && cli.cwd.is_none();
let action_and_target_session_if_resume_or_fork = match &session_selection {
resume_picker::SessionSelection::Resume(target_session) => {
Some((CwdPromptAction::Resume, target_session))
@@ -1508,7 +1589,7 @@ async fn run_ratatui_app(
};
let fallback_cwd = match action_and_target_session_if_resume_or_fork {
Some((action, target_session)) => {
if remote_mode {
if uses_remote_workspace {
Some(current_cwd.to_path_buf())
} else {
match resolve_cwd_for_resume_or_fork(
@@ -1614,8 +1695,10 @@ async fn run_ratatui_app(
)
.await
{
Ok(app_server) => AppServerSession::new(app_server)
.with_remote_cwd_override(remote_cwd_override.clone()),
Ok(app_server) => {
AppServerSession::new(app_server, app_server_target.thread_params_mode())
.with_remote_cwd_override(remote_cwd_override.clone())
}
Err(err) => {
terminal_restore_guard.restore_silently();
session_log::log_session_end();
@@ -1645,7 +1728,7 @@ async fn run_ratatui_app(
should_show_trust_screen, // Proxy to: is it a first run in this directory?
should_show_trust_screen_flag, // Preserve the startup-time trust NUX signal before onboarding
should_prompt_windows_sandbox_nux_at_startup,
remote_endpoint,
app_server_target,
state_db,
environment_manager,
startup_hooks_browser,
@@ -1989,6 +2072,117 @@ mod tests {
Ok(())
}
#[test]
fn app_server_target_for_launch_uses_local_daemon_for_default_socket() -> color_eyre::Result<()>
{
let socket_path = AbsolutePathBuf::relative_to_current_dir("codex.sock")?;
let target = app_server_target_for_launch(
/*explicit_remote_endpoint*/ None,
Some(socket_path.clone()),
/*can_reuse_implicit_local_daemon*/ true,
);
assert_eq!(
target,
AppServerTarget::LocalDaemon {
endpoint: RemoteAppServerEndpoint::UnixSocket { socket_path },
}
);
assert!(!target.uses_remote_workspace());
assert_eq!(target.thread_params_mode(), ThreadParamsMode::Embedded);
Ok(())
}
#[test]
fn app_server_target_for_launch_prefers_explicit_remote_endpoint() -> color_eyre::Result<()> {
let explicit_endpoint = RemoteAppServerEndpoint::UnixSocket {
socket_path: AbsolutePathBuf::relative_to_current_dir("explicit.sock")?,
};
let target = app_server_target_for_launch(
Some(explicit_endpoint.clone()),
Some(AbsolutePathBuf::relative_to_current_dir("default.sock")?),
/*can_reuse_implicit_local_daemon*/ false,
);
assert_eq!(
target,
AppServerTarget::Remote {
endpoint: explicit_endpoint,
}
);
assert!(target.uses_remote_workspace());
assert_eq!(target.thread_params_mode(), ThreadParamsMode::Remote);
Ok(())
}
#[test]
fn app_server_target_for_launch_skips_local_daemon_when_launch_config_is_not_replayable()
-> color_eyre::Result<()> {
let socket_path = AbsolutePathBuf::relative_to_current_dir("codex.sock")?;
let target = app_server_target_for_launch(
/*explicit_remote_endpoint*/ None,
Some(socket_path),
/*can_reuse_implicit_local_daemon*/ false,
);
assert_eq!(target, AppServerTarget::Embedded);
Ok(())
}
#[test]
fn can_reuse_implicit_local_daemon_requires_default_launch_config() -> color_eyre::Result<()> {
let mut loader_overrides = LoaderOverrides::default();
let cli_kv_overrides = vec![("web_search".to_string(), toml::Value::String("live".into()))];
assert!(can_reuse_implicit_local_daemon(
&[],
&LoaderOverrides::default(),
/*strict_config*/ false,
/*has_non_replayable_launch_overrides*/ false,
));
assert!(!can_reuse_implicit_local_daemon(
&cli_kv_overrides,
&LoaderOverrides::default(),
/*strict_config*/ false,
/*has_non_replayable_launch_overrides*/ false,
));
loader_overrides.ignore_user_config = true;
assert!(!can_reuse_implicit_local_daemon(
&[],
&loader_overrides,
/*strict_config*/ false,
/*has_non_replayable_launch_overrides*/ false,
));
assert!(!can_reuse_implicit_local_daemon(
&[],
&LoaderOverrides::default(),
/*strict_config*/ true,
/*has_non_replayable_launch_overrides*/ false,
));
assert!(!can_reuse_implicit_local_daemon(
&[],
&LoaderOverrides::default(),
/*strict_config*/ false,
/*has_non_replayable_launch_overrides*/ true,
));
Ok(())
}
#[test]
fn should_load_configured_environments_for_local_daemon() -> color_eyre::Result<()> {
let target = AppServerTarget::LocalDaemon {
endpoint: RemoteAppServerEndpoint::UnixSocket {
socket_path: AbsolutePathBuf::relative_to_current_dir("codex.sock")?,
},
};
assert!(should_load_configured_environments(
&LoaderOverrides::default(),
&target,
));
Ok(())
}
#[tokio::test]
async fn latest_session_lookup_params_keep_local_filters_for_embedded_sessions()
-> std::io::Result<()> {
@@ -1997,7 +2191,34 @@ mod tests {
let cwd = temp_dir.path().join("project");
let params = latest_session_lookup_params(
/*is_remote*/ false,
/*uses_remote_workspace*/ false,
&config,
Some(cwd.as_path()),
/*include_non_interactive*/ false,
);
assert_eq!(params.model_providers, Some(vec![config.model_provider_id]));
assert_eq!(
params.cwd,
Some(ThreadListCwdFilter::One(cwd.to_string_lossy().to_string()))
);
Ok(())
}
#[tokio::test]
async fn latest_session_lookup_params_keep_local_filters_for_local_daemon_sessions()
-> color_eyre::Result<()> {
let temp_dir = TempDir::new()?;
let config = build_config(&temp_dir).await?;
let cwd = temp_dir.path().join("project");
let target = AppServerTarget::LocalDaemon {
endpoint: RemoteAppServerEndpoint::UnixSocket {
socket_path: AbsolutePathBuf::relative_to_current_dir("codex.sock")?,
},
};
let params = latest_session_lookup_params(
target.uses_remote_workspace(),
&config,
Some(cwd.as_path()),
/*include_non_interactive*/ false,
@@ -2018,7 +2239,7 @@ mod tests {
let config = build_config(&temp_dir).await?;
let params = latest_session_lookup_params(
/*is_remote*/ true, &config, /*cwd_filter*/ None,
/*uses_remote_workspace*/ true, &config, /*cwd_filter*/ None,
/*include_non_interactive*/ false,
);
@@ -2035,7 +2256,7 @@ mod tests {
let cwd = Path::new("repo/on/server");
let params = latest_session_lookup_params(
/*is_remote*/ true,
/*uses_remote_workspace*/ true,
&config,
Some(cwd),
/*include_non_interactive*/ false,
@@ -2056,15 +2277,15 @@ mod tests {
let remote_cwd = Path::new("repo/on/server");
let local_filter = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*uses_remote_workspace*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ false,
);
let show_all_filter = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*uses_remote_workspace*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ true,
);
let remote_filter = latest_session_cwd_filter(
/*remote_mode*/ true,
/*uses_remote_workspace*/ true,
Some(remote_cwd),
&config,
/*show_all*/ false,
@@ -2189,12 +2410,14 @@ mod tests {
&other_cwd,
)?;
let mut app_server =
AppServerSession::new(codex_app_server_client::AppServerClient::InProcess(
let mut app_server = AppServerSession::new(
codex_app_server_client::AppServerClient::InProcess(
start_test_embedded_app_server(config.clone()).await?,
));
),
ThreadParamsMode::Embedded,
);
let filter_cwd = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*uses_remote_workspace*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ false,
);
let scoped_target = lookup_latest_session_target_with_app_server(
@@ -2206,7 +2429,7 @@ mod tests {
.await?
.expect("expected project-scoped fork --last target");
let show_all_filter_cwd = latest_session_cwd_filter(
/*remote_mode*/ false, /*remote_cwd_override*/ None, &config,
/*uses_remote_workspace*/ false, /*remote_cwd_override*/ None, &config,
/*show_all*/ true,
);
let show_all_target = lookup_latest_session_target_with_app_server(
@@ -2265,6 +2488,29 @@ mod tests {
Ok(())
}
#[tokio::test]
async fn config_cwd_for_app_server_target_canonicalizes_local_daemon_cli_cwd()
-> std::io::Result<()> {
let temp_dir = TempDir::new()?;
let target = AppServerTarget::LocalDaemon {
endpoint: RemoteAppServerEndpoint::UnixSocket {
socket_path: AbsolutePathBuf::relative_to_current_dir("codex.sock")?,
},
};
let environment_manager = EnvironmentManager::default_for_tests();
let config_cwd =
config_cwd_for_app_server_target(Some(temp_dir.path()), &target, &environment_manager)?;
assert_eq!(
config_cwd,
Some(AbsolutePathBuf::from_absolute_path(dunce::canonicalize(
temp_dir.path()
)?)?)
);
Ok(())
}
#[tokio::test]
async fn config_cwd_for_app_server_target_errors_for_missing_embedded_cli_cwd()
-> std::io::Result<()> {
@@ -2388,10 +2634,12 @@ mod tests {
.await
.map_err(std::io::Error::other)?;
let mut app_server =
AppServerSession::new(codex_app_server_client::AppServerClient::InProcess(
let mut app_server = AppServerSession::new(
codex_app_server_client::AppServerClient::InProcess(
start_test_embedded_app_server(config).await?,
));
),
ThreadParamsMode::Embedded,
);
let target =
lookup_session_target_by_name_with_app_server(&mut app_server, "saved-session")
.await?;
+24 -16
View File
@@ -349,15 +349,15 @@ async fn run_resume_picker_with_launch_context(
launch_context: SessionPickerLaunchContext,
) -> Result<SessionSelection> {
let (bg_tx, bg_rx) = mpsc::unbounded_channel();
let is_remote = app_server.is_remote();
let uses_remote_workspace = app_server.uses_remote_workspace();
let cwd_filter = picker_cwd_filter(
config.cwd.as_path(),
/*show_all*/ false,
is_remote,
uses_remote_workspace,
app_server.remote_cwd_override(),
);
let local_filter_cwd = local_picker_cwd_filter(&cwd_filter, is_remote);
let provider_filter = picker_provider_filter(config, is_remote);
let local_filter_cwd = local_picker_cwd_filter(&cwd_filter, uses_remote_workspace);
let provider_filter = picker_provider_filter(config, uses_remote_workspace);
let runtime_keymap = picker_runtime_keymap(config)?;
let options = SessionPickerRunOptions {
show_all,
@@ -395,15 +395,15 @@ pub async fn run_fork_picker_with_app_server(
app_server: AppServerSession,
) -> Result<SessionSelection> {
let (bg_tx, bg_rx) = mpsc::unbounded_channel();
let is_remote = app_server.is_remote();
let uses_remote_workspace = app_server.uses_remote_workspace();
let cwd_filter = picker_cwd_filter(
config.cwd.as_path(),
/*show_all*/ false,
is_remote,
uses_remote_workspace,
app_server.remote_cwd_override(),
);
let local_filter_cwd = local_picker_cwd_filter(&cwd_filter, is_remote);
let provider_filter = picker_provider_filter(config, is_remote);
let local_filter_cwd = local_picker_cwd_filter(&cwd_filter, uses_remote_workspace);
let provider_filter = picker_provider_filter(config, uses_remote_workspace);
let runtime_keymap = picker_runtime_keymap(config)?;
let options = SessionPickerRunOptions {
show_all,
@@ -513,12 +513,19 @@ fn raw_reasoning_visibility(config: &Config) -> RawReasoningVisibility {
}
}
fn local_picker_cwd_filter(cwd_filter: &Option<PathBuf>, is_remote: bool) -> Option<PathBuf> {
if is_remote { None } else { cwd_filter.clone() }
fn local_picker_cwd_filter(
cwd_filter: &Option<PathBuf>,
uses_remote_workspace: bool,
) -> Option<PathBuf> {
if uses_remote_workspace {
None
} else {
cwd_filter.clone()
}
}
fn picker_provider_filter(config: &Config, is_remote: bool) -> ProviderFilter {
if is_remote {
fn picker_provider_filter(config: &Config, uses_remote_workspace: bool) -> ProviderFilter {
if uses_remote_workspace {
ProviderFilter::Any
} else {
ProviderFilter::MatchDefault(config.model_provider_id.to_string())
@@ -533,12 +540,12 @@ fn picker_runtime_keymap(config: &Config) -> Result<RuntimeKeymap> {
fn picker_cwd_filter(
config_cwd: &Path,
show_all: bool,
is_remote: bool,
uses_remote_workspace: bool,
remote_cwd_override: Option<&Path>,
) -> Option<PathBuf> {
if show_all {
None
} else if is_remote {
} else if uses_remote_workspace {
remote_cwd_override.map(Path::to_path_buf)
} else {
Some(config_cwd.to_path_buf())
@@ -3309,7 +3316,7 @@ mod tests {
let cwd_filter = picker_cwd_filter(
Path::new("/tmp/project"),
/*show_all*/ false,
/*is_remote*/ false,
/*uses_remote_workspace*/ false,
/*remote_cwd_override*/ None,
);
let params = thread_list_params(
@@ -3588,7 +3595,8 @@ mod tests {
remote_cwd.clone(),
SessionPickerAction::Resume,
);
state.local_filter_cwd = local_picker_cwd_filter(&remote_cwd, /*is_remote*/ true);
state.local_filter_cwd =
local_picker_cwd_filter(&remote_cwd, /*uses_remote_workspace*/ true);
state.start_initial_load();