mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Add session delete commands in CLI and TUI (#27476)
## Summary The app server exposes `thread/delete`, but users cannot invoke it from the CLI or TUI. Because deletion is irreversible, the user-facing commands need deliberate confirmation and safer handling of name-based targets. - Add `codex delete <SESSION>` with interactive confirmation, restricting `--force` to UUID targets. - Resolve exact names across active and archived sessions, including renamed sessions, and validate prompted UUID targets before confirmation. - Add a `/delete` command with a confirmation popup that warns the current session and its subagent threads will be permanently deleted. ## Manual testing - Deleted by UUID with `--force` and verified the rollout, session-index entry, and database row were removed. - Exercised name-based confirmation for both cancellation and affirmative deletion; cancellation preserved the session and confirmation removed it. - Verified deletion refuses to proceed without `--force`, while `--force` rejects names, including duplicate names. - Verified duplicate-name confirmation displays the concrete UUID selected. - Deleted an archived session by name. - Verified an already-missing UUID fails before displaying a confirmation prompt. - Exercised `/delete` in the TUI: the popup defaults to No, cancellation preserves the session, and confirmation deletes the session and exits. - Verified that `codex delete` works for both archived and non-archived sessions.
This commit is contained in:
committed by
GitHub
Unverified
parent
36fc79c6f4
commit
9d87b771ce
@@ -180,6 +180,9 @@ enum Subcommand {
|
||||
/// Archive a saved session by id or session name.
|
||||
Archive(SessionArchiveCommand),
|
||||
|
||||
/// Permanently delete a saved session by id or session name.
|
||||
Delete(DeleteCommand),
|
||||
|
||||
/// Unarchive a saved session by id or session name.
|
||||
Unarchive(SessionArchiveCommand),
|
||||
|
||||
@@ -354,6 +357,16 @@ struct SessionArchiveConfigOverrides {
|
||||
config_overrides: CliConfigOverrides,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct DeleteCommand {
|
||||
#[clap(flatten)]
|
||||
session: SessionArchiveCommand,
|
||||
|
||||
/// Delete without prompting. SESSION must be a UUID.
|
||||
#[arg(long, default_value_t = false)]
|
||||
force: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct ForkCommand {
|
||||
/// Conversation/session id (UUID). When provided, forks this session.
|
||||
@@ -829,6 +842,17 @@ async fn run_session_archive_cli_command(
|
||||
.map_err(|err| anyhow::anyhow!("{err}"))
|
||||
}
|
||||
|
||||
fn delete_action(target: &str, force: bool) -> anyhow::Result<codex_tui::SessionArchiveAction> {
|
||||
if force && codex_protocol::ThreadId::from_string(target).is_err() {
|
||||
anyhow::bail!("--force requires a session UUID; names must be confirmed interactively");
|
||||
}
|
||||
let confirmation = match force {
|
||||
true => codex_tui::DeleteConfirmation::Skip,
|
||||
false => codex_tui::DeleteConfirmation::Prompt,
|
||||
};
|
||||
Ok(codex_tui::SessionArchiveAction::Delete(confirmation))
|
||||
}
|
||||
|
||||
async fn run_debug_app_server_command(cmd: DebugAppServerCommand) -> anyhow::Result<()> {
|
||||
match cmd.subcommand {
|
||||
DebugAppServerSubcommand::SendMessageV2(cmd) => {
|
||||
@@ -1233,6 +1257,20 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
.await?;
|
||||
println!("{output}");
|
||||
}
|
||||
Some(Subcommand::Delete(DeleteCommand { session, force })) => {
|
||||
let action = delete_action(&session.target, force)?;
|
||||
let output = run_session_archive_cli_command(
|
||||
action,
|
||||
session,
|
||||
interactive,
|
||||
root_config_overrides.clone(),
|
||||
root_remote.clone(),
|
||||
root_remote_auth_token_env.clone(),
|
||||
arg0_paths.clone(),
|
||||
)
|
||||
.await?;
|
||||
println!("{output}");
|
||||
}
|
||||
Some(Subcommand::Unarchive(cmd)) => {
|
||||
let output = run_session_archive_cli_command(
|
||||
codex_tui::SessionArchiveAction::Unarchive,
|
||||
@@ -1597,6 +1635,7 @@ fn profile_v2_for_subcommand<'a>(
|
||||
| Subcommand::Review(_)
|
||||
| Subcommand::Resume(_)
|
||||
| Subcommand::Archive(_)
|
||||
| Subcommand::Delete(_)
|
||||
| Subcommand::Unarchive(_)
|
||||
| Subcommand::Fork(_)
|
||||
| Subcommand::Mcp(_)
|
||||
@@ -1605,7 +1644,7 @@ fn profile_v2_for_subcommand<'a>(
|
||||
subcommand: DebugSubcommand::PromptInput(_),
|
||||
}) => Ok(Some(profile_v2)),
|
||||
_ => anyhow::bail!(
|
||||
"--profile only applies to runtime commands and `codex mcp`: `codex`, `codex exec`, `codex review`, `codex resume`, `codex archive`, `codex unarchive`, `codex fork`, `codex mcp`, `codex sandbox`, and `codex debug prompt-input`."
|
||||
"--profile only applies to runtime commands and `codex mcp`: `codex`, `codex exec`, `codex review`, `codex resume`, `codex archive`, `codex delete`, `codex unarchive`, `codex fork`, `codex mcp`, `codex sandbox`, and `codex debug prompt-input`."
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -2026,6 +2065,7 @@ fn unsupported_subcommand_name_for_strict_config(
|
||||
| Some(Subcommand::ExecServer(_))
|
||||
| Some(Subcommand::Resume(_))
|
||||
| Some(Subcommand::Archive(_))
|
||||
| Some(Subcommand::Delete(_))
|
||||
| Some(Subcommand::Unarchive(_))
|
||||
| Some(Subcommand::Fork(_))
|
||||
| Some(Subcommand::Doctor(_)) => None,
|
||||
@@ -2868,6 +2908,17 @@ mod tests {
|
||||
assert!(interactive.bypass_hook_trust);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_force_requires_uuid() {
|
||||
assert!(delete_action("123e4567-e89b-12d3-a456-426614174000", true).is_ok());
|
||||
|
||||
let err = delete_action("my-thread", true).expect_err("name should require prompt");
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"--force requires a session UUID; names must be confirmed interactively"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
|
||||
#[test]
|
||||
fn sandbox_parses_permissions_profile() {
|
||||
|
||||
Reference in New Issue
Block a user