tui: exit session on Ctrl+C in cwd change prompt (#12040)

## Summary
- change the cwd-change prompt (shown when resuming/forking across
different directories) so `Ctrl+C`/`Ctrl+D` exits the session instead of
implicitly selecting "Use session directory"
- introduce explicit prompt and resolver exit outcomes so this intent is
propagated cleanly through both startup resume/fork and in-app `/resume`
flows
- add a unit test that verifies `Ctrl+C` exits rather than selecting an
option

## Why
Previously, pressing `Ctrl+C` on this prompt silently picked one of the
options, which made it hard to abort. This aligns the prompt with the
expected quit behavior.

## Codex author
`codex resume 019c6d39-bbfb-7dc3-8008-1388a054e86d`
This commit is contained in:
Charley Cunningham
2026-02-17 14:48:12 -08:00
committed by GitHub
parent c4bb7db159
commit 709e2133bb
3 changed files with 67 additions and 15 deletions

View File

@@ -44,6 +44,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_oss::ensure_oss_provider_ready;
use codex_utils_oss::get_default_model_for_oss_provider;
use cwd_prompt::CwdPromptAction;
use cwd_prompt::CwdPromptOutcome;
use cwd_prompt::CwdSelection;
use std::fs::OpenOptions;
use std::path::Path;
@@ -672,8 +673,22 @@ async fn run_ratatui_app(
};
let fallback_cwd = match action_and_path_if_resume_or_fork {
Some((action, path)) => {
resolve_cwd_for_resume_or_fork(&mut tui, &current_cwd, path, action, allow_prompt)
match resolve_cwd_for_resume_or_fork(&mut tui, &current_cwd, path, action, allow_prompt)
.await?
{
ResolveCwdOutcome::Continue(cwd) => cwd,
ResolveCwdOutcome::Exit => {
restore();
session_log::log_session_end();
return Ok(AppExitInfo {
token_usage: codex_core::protocol::TokenUsage::default(),
thread_id: None,
thread_name: None,
update_action: None,
exit_reason: ExitReason::UserRequested,
});
}
}
}
None => None,
};
@@ -780,25 +795,35 @@ pub(crate) fn cwds_differ(current_cwd: &Path, session_cwd: &Path) -> bool {
}
}
pub(crate) enum ResolveCwdOutcome {
Continue(Option<PathBuf>),
Exit,
}
pub(crate) async fn resolve_cwd_for_resume_or_fork(
tui: &mut Tui,
current_cwd: &Path,
path: &Path,
action: CwdPromptAction,
allow_prompt: bool,
) -> color_eyre::Result<Option<PathBuf>> {
) -> color_eyre::Result<ResolveCwdOutcome> {
let Some(history_cwd) = read_session_cwd(path).await else {
return Ok(None);
return Ok(ResolveCwdOutcome::Continue(None));
};
if allow_prompt && cwds_differ(current_cwd, &history_cwd) {
let selection =
let selection_outcome =
cwd_prompt::run_cwd_selection_prompt(tui, action, current_cwd, &history_cwd).await?;
return Ok(Some(match selection {
CwdSelection::Current => current_cwd.to_path_buf(),
CwdSelection::Session => history_cwd,
}));
return Ok(match selection_outcome {
CwdPromptOutcome::Selection(CwdSelection::Current) => {
ResolveCwdOutcome::Continue(Some(current_cwd.to_path_buf()))
}
CwdPromptOutcome::Selection(CwdSelection::Session) => {
ResolveCwdOutcome::Continue(Some(history_cwd))
}
CwdPromptOutcome::Exit => ResolveCwdOutcome::Exit,
});
}
Ok(Some(history_cwd))
Ok(ResolveCwdOutcome::Continue(Some(history_cwd)))
}
#[expect(