mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
Add managed worktrees to codex exec (#42652)
## 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
This commit is contained in:
1 parent
f46671b14a
commit
eb5a00b068
18 files changed
+631
-9
No files matched your search
@@ -1060,6 +1060,7 @@ async fn cli_main(
|
||||
mut interactive,
|
||||
subcommand,
|
||||
} = MultitoolCli::parse();
|
||||
reject_unsupported_worktree_for_subcommand(interactive.shared.worktree, &subcommand)?;
|
||||
// Fold --enable/--disable into config overrides so they flow to all subcommands.
|
||||
let toggle_overrides = feature_toggles.to_overrides()?;
|
||||
root_config_overrides.raw_overrides.extend(toggle_overrides);
|
||||
@@ -2395,6 +2396,42 @@ fn reject_remote_mode_for_subcommand(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reject_unsupported_worktree_for_subcommand(
|
||||
root_worktree: bool,
|
||||
subcommand: &Option<Subcommand>,
|
||||
) -> anyhow::Result<()> {
|
||||
let subcommand_worktree = match subcommand {
|
||||
Some(Subcommand::Exec(command)) => command.shared.worktree,
|
||||
Some(Subcommand::Resume(command)) => command.config_overrides.0.shared.worktree,
|
||||
Some(Subcommand::Fork(command)) => command.config_overrides.0.shared.worktree,
|
||||
Some(Subcommand::Archive(command)) | Some(Subcommand::Unarchive(command)) => {
|
||||
command.config_overrides.shared.worktree
|
||||
}
|
||||
Some(Subcommand::Delete(command)) => command.session.config_overrides.shared.worktree,
|
||||
Some(Subcommand::Queue(command)) => command.config_overrides.shared.worktree,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if !root_worktree && !subcommand_worktree {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match subcommand {
|
||||
Some(Subcommand::Exec(command)) => match &command.command {
|
||||
None | Some(ExecCommand::Fork(_)) => Ok(()),
|
||||
Some(ExecCommand::Resume(_)) => anyhow::bail!(
|
||||
"`--worktree` cannot resume an existing session; use `codex exec fork --worktree`"
|
||||
),
|
||||
Some(ExecCommand::Review(_)) => {
|
||||
anyhow::bail!("`--worktree` is not supported for code review")
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
anyhow::bail!("`--worktree` currently supports only `codex exec` and `codex exec fork`")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reject_root_strict_config_for_subcommand(
|
||||
strict_config: bool,
|
||||
subcommand: &Option<Subcommand>,
|
||||
@@ -3211,6 +3248,66 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_worktree_flag_supports_root_local_and_nested_fork_positions() {
|
||||
let arguments = [
|
||||
vec!["codex", "--worktree", "exec", "hello"],
|
||||
vec!["codex", "exec", "--worktree", "hello"],
|
||||
vec![
|
||||
"codex",
|
||||
"exec",
|
||||
"fork",
|
||||
"--worktree",
|
||||
"019f1234-5678-7000-8000-000000000001",
|
||||
],
|
||||
];
|
||||
|
||||
for arguments in arguments {
|
||||
let cli = MultitoolCli::try_parse_from(&arguments).expect("parse worktree command");
|
||||
assert!(
|
||||
reject_unsupported_worktree_for_subcommand(
|
||||
cli.interactive.shared.worktree,
|
||||
&cli.subcommand,
|
||||
)
|
||||
.is_ok(),
|
||||
"headless worktree command should be accepted: {arguments:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn worktree_flag_rejects_unsupported_session_and_management_commands() {
|
||||
let arguments = [
|
||||
vec!["codex", "--worktree"],
|
||||
vec!["codex", "--worktree", "login"],
|
||||
vec!["codex", "exec", "resume", "--worktree", "session"],
|
||||
vec!["codex", "exec", "review", "--worktree"],
|
||||
vec!["codex", "resume", "--worktree", "session"],
|
||||
vec!["codex", "archive", "session", "--worktree"],
|
||||
vec![
|
||||
"codex",
|
||||
"queue",
|
||||
"--thread",
|
||||
"session",
|
||||
"--message",
|
||||
"hi",
|
||||
"--worktree",
|
||||
],
|
||||
];
|
||||
|
||||
for arguments in arguments {
|
||||
let cli = MultitoolCli::try_parse_from(&arguments).expect("parse shared worktree flag");
|
||||
assert!(
|
||||
reject_unsupported_worktree_for_subcommand(
|
||||
cli.interactive.shared.worktree,
|
||||
&cli.subcommand,
|
||||
)
|
||||
.is_err(),
|
||||
"unsupported command must be rejected: {arguments:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_resume_last_accepts_prompt_positional() {
|
||||
let cli =
|
||||
|
||||
Reference in new issue
Block a user