mirror of
https://github.com/openai/codex.git
synced 2026-09-11 20:36:49 +00:00
## What changed - Add the experimental `worktrees` feature and a shared `--worktree` flag for new and forked `codex exec` sessions. - Create each enabled session in a managed Git worktree, use that checkout as the session working directory, and bind the checkout to the new thread. - Share the configured worktree pool with Desktop while leaving automatic cleanup disabled for CLI allocations. - Reject unsupported commands, remote execution, ignored user configuration, ephemeral sessions, and use without the feature enabled before allocating a worktree. ## Testing - Cover flag placement and inheritance, supported and rejected command combinations, worktree allocation and thread ownership, configuration gating, and compatibility with existing worktree-backed sessions. GitOrigin-RevId: 011ff4639b09e8992c50d7b823df23e71798670e
148 lines
4.0 KiB
Rust
148 lines
4.0 KiB
Rust
use super::*;
|
|
use pretty_assertions::assert_eq;
|
|
|
|
#[test]
|
|
fn resume_parses_prompt_after_global_flags() {
|
|
const PROMPT: &str = "echo resume-with-global-flags-after-subcommand";
|
|
let cli = Cli::parse_from([
|
|
"codex-exec",
|
|
"resume",
|
|
"--last",
|
|
"--json",
|
|
"--model",
|
|
"gpt-5.2-codex",
|
|
"--dangerously-bypass-approvals-and-sandbox",
|
|
"--skip-git-repo-check",
|
|
"--ephemeral",
|
|
"--ignore-user-config",
|
|
"--ignore-rules",
|
|
PROMPT,
|
|
]);
|
|
|
|
assert!(cli.ephemeral);
|
|
assert!(cli.ignore_user_config);
|
|
assert!(cli.ignore_rules);
|
|
let Some(Command::Resume(args)) = cli.command else {
|
|
panic!("expected resume command");
|
|
};
|
|
let effective_prompt = args.prompt.clone().or_else(|| {
|
|
if args.last {
|
|
args.session_id.clone()
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
assert_eq!(effective_prompt.as_deref(), Some(PROMPT));
|
|
}
|
|
|
|
#[test]
|
|
fn resume_accepts_output_flags_after_subcommand() {
|
|
const PROMPT: &str = "echo resume-with-output-file";
|
|
let cli = Cli::parse_from([
|
|
"codex-exec",
|
|
"resume",
|
|
"session-123",
|
|
"-o",
|
|
"/tmp/resume-output.md",
|
|
"--output-schema",
|
|
"/tmp/schema.json",
|
|
PROMPT,
|
|
]);
|
|
|
|
assert_eq!(
|
|
cli.last_message_file,
|
|
Some(PathBuf::from("/tmp/resume-output.md"))
|
|
);
|
|
assert_eq!(cli.output_schema, Some(PathBuf::from("/tmp/schema.json")));
|
|
let Some(Command::Resume(args)) = cli.command else {
|
|
panic!("expected resume command");
|
|
};
|
|
assert_eq!(args.session_id.as_deref(), Some("session-123"));
|
|
assert_eq!(args.prompt.as_deref(), Some(PROMPT));
|
|
}
|
|
|
|
#[test]
|
|
fn fork_parses_prompt_after_global_flags() {
|
|
const PROMPT: &str = "continue on the fork";
|
|
let cli = Cli::parse_from([
|
|
"codex-exec",
|
|
"fork",
|
|
"session-123",
|
|
"--json",
|
|
"--model",
|
|
"gpt-5.2-codex",
|
|
"--thread-source",
|
|
"automated_review",
|
|
"--skip-git-repo-check",
|
|
"--ephemeral",
|
|
PROMPT,
|
|
]);
|
|
|
|
assert!(cli.json);
|
|
assert!(cli.ephemeral);
|
|
assert_eq!(
|
|
cli.thread_source,
|
|
Some(ThreadSource::Feature("automated_review".to_string()))
|
|
);
|
|
let Some(Command::Fork(args)) = cli.command else {
|
|
panic!("expected fork command");
|
|
};
|
|
assert_eq!(args.session_id, "session-123");
|
|
assert_eq!(args.prompt.as_deref(), Some(PROMPT));
|
|
}
|
|
|
|
#[test]
|
|
fn parses_config_isolation_flags() {
|
|
let cli = Cli::parse_from([
|
|
"codex-exec",
|
|
"--ignore-user-config",
|
|
"--ignore-rules",
|
|
"summarize",
|
|
]);
|
|
|
|
assert!(cli.ignore_user_config);
|
|
assert!(cli.ignore_rules);
|
|
}
|
|
|
|
#[test]
|
|
fn approve_for_me_flag_applies_to_resume_when_passed_at_exec_root() {
|
|
for flag in ["--approve-for-me", "--not-so-yolo"] {
|
|
let cli = Cli::parse_from(["codex-exec", flag, "resume", "--last"]);
|
|
|
|
assert!(cli.auto_review);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn approve_for_me_flag_conflicts_with_other_sandbox_modes() {
|
|
for conflicting_args in [
|
|
vec!["--sandbox", "read-only"],
|
|
vec!["--dangerously-bypass-approvals-and-sandbox"],
|
|
] {
|
|
let mut args = vec!["codex-exec", "--approve-for-me"];
|
|
args.extend(conflicting_args);
|
|
args.push("summarize");
|
|
|
|
let error = Cli::try_parse_from(args).expect_err("flags should conflict");
|
|
assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn worktree_flag_is_accepted_after_fork_subcommand() {
|
|
let cli = Cli::try_parse_from(["codex-exec", "fork", "session-id", "--worktree"])
|
|
.expect("worktree should be a global exec argument");
|
|
|
|
assert!(cli.worktree);
|
|
assert!(matches!(cli.command, Some(Command::Fork(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn worktree_flag_is_accepted_before_fork_subcommand() {
|
|
let cli = Cli::try_parse_from(["codex-exec", "--worktree", "fork", "session-id"])
|
|
.expect("worktree should be accepted before the fork subcommand");
|
|
|
|
assert!(cli.worktree);
|
|
assert!(matches!(cli.command, Some(Command::Fork(_))));
|
|
}
|