Fix resume --last with --json option (#9475)

Fix resume --last prompt parsing by dropping the clap conflict on the
codex resume subcommand so a positional prompt is accepted when --last
is set. This aligns interactive resume behavior with exec-mode logic and
avoids the “--last cannot be used with SESSION_ID” error.

This addresses #6717
This commit is contained in:
Eric Traut
2026-01-26 20:20:57 -08:00
committed by GitHub
parent f45a8733bf
commit 7c96f2e84c
2 changed files with 88 additions and 7 deletions

View File

@@ -147,7 +147,7 @@ struct ResumeCommand {
session_id: Option<String>,
/// Continue the most recent session without showing the picker.
#[arg(long = "last", default_value_t = false, conflicts_with = "session_id")]
#[arg(long = "last", default_value_t = false)]
last: bool,
/// Show all sessions (disables cwd filtering and shows CWD column).
@@ -932,6 +932,24 @@ mod tests {
finalize_fork_interactive(interactive, root_overrides, session_id, last, all, fork_cli)
}
#[test]
fn exec_resume_last_accepts_prompt_positional() {
let cli =
MultitoolCli::try_parse_from(["codex", "exec", "--json", "resume", "--last", "2+2"])
.expect("parse should succeed");
let Some(Subcommand::Exec(exec)) = cli.subcommand else {
panic!("expected exec subcommand");
};
let Some(codex_exec::Command::Resume(args)) = exec.command else {
panic!("expected exec resume");
};
assert!(args.last);
assert_eq!(args.session_id, None);
assert_eq!(args.prompt.as_deref(), Some("2+2"));
}
fn app_server_from_args(args: &[&str]) -> AppServerCommand {
let cli = MultitoolCli::try_parse_from(args).expect("parse");
let Subcommand::AppServer(app_server) = cli.subcommand.expect("app-server present") else {

View File

@@ -1,3 +1,5 @@
use clap::Args;
use clap::FromArgMatches;
use clap::Parser;
use clap::ValueEnum;
use codex_common::CliConfigOverrides;
@@ -108,20 +110,22 @@ pub enum Command {
Review(ReviewArgs),
}
#[derive(Parser, Debug)]
pub struct ResumeArgs {
#[derive(Args, Debug)]
struct ResumeArgsRaw {
// Note: This is the direct clap shape. We reinterpret the positional when --last is set
// so "codex resume --last <prompt>" treats the positional as a prompt, not a session id.
/// Conversation/session id (UUID). When provided, resumes this session.
/// If omitted, use --last to pick the most recent recorded session.
#[arg(value_name = "SESSION_ID")]
pub session_id: Option<String>,
session_id: Option<String>,
/// Resume the most recent recorded session (newest) without specifying an id.
#[arg(long = "last", default_value_t = false)]
pub last: bool,
last: bool,
/// Show all sessions (disables cwd filtering).
#[arg(long = "all", default_value_t = false)]
pub all: bool,
all: bool,
/// Optional image(s) to attach to the prompt sent after resuming.
#[arg(
@@ -131,13 +135,72 @@ pub struct ResumeArgs {
value_delimiter = ',',
num_args = 1
)]
pub images: Vec<PathBuf>,
images: Vec<PathBuf>,
/// Prompt to send after resuming the session. If `-` is used, read from stdin.
#[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]
prompt: Option<String>,
}
#[derive(Debug)]
pub struct ResumeArgs {
/// Conversation/session id (UUID). When provided, resumes this session.
/// If omitted, use --last to pick the most recent recorded session.
pub session_id: Option<String>,
/// Resume the most recent recorded session (newest) without specifying an id.
pub last: bool,
/// Show all sessions (disables cwd filtering).
pub all: bool,
/// Optional image(s) to attach to the prompt sent after resuming.
pub images: Vec<PathBuf>,
/// Prompt to send after resuming the session. If `-` is used, read from stdin.
pub prompt: Option<String>,
}
impl From<ResumeArgsRaw> for ResumeArgs {
fn from(raw: ResumeArgsRaw) -> Self {
// When --last is used without an explicit prompt, treat the positional as the prompt
// (clap cant express this conditional positional meaning cleanly).
let (session_id, prompt) = if raw.last && raw.prompt.is_none() {
(None, raw.session_id)
} else {
(raw.session_id, raw.prompt)
};
Self {
session_id,
last: raw.last,
all: raw.all,
images: raw.images,
prompt,
}
}
}
impl Args for ResumeArgs {
fn augment_args(cmd: clap::Command) -> clap::Command {
ResumeArgsRaw::augment_args(cmd)
}
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
ResumeArgsRaw::augment_args_for_update(cmd)
}
}
impl FromArgMatches for ResumeArgs {
fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
ResumeArgsRaw::from_arg_matches(matches).map(Self::from)
}
fn update_from_arg_matches(&mut self, matches: &clap::ArgMatches) -> Result<(), clap::Error> {
*self = ResumeArgsRaw::from_arg_matches(matches).map(Self::from)?;
Ok(())
}
}
#[derive(Parser, Debug)]
pub struct ReviewArgs {
/// Review staged, unstaged, and untracked changes.