mirror of
https://github.com/openai/codex.git
synced 2026-11-09 20:50:33 +00:00
Support managed worktrees for interactive sessions and forks (#43069)
## Why `--worktree` previously supported only `codex exec` and `codex exec fork`. Interactive sessions need the same managed checkout support, with configuration and policy resolved for the destination before starting a turn. ## What changed - Enable `codex --worktree` and `codex fork --worktree <session-id>` behind the `worktrees` feature, restricted to local sessions. Interactive forks require an explicit session selector. - Resolve interactive and exec worktree forks from the session's latest saved working directory unless `--cd` is supplied. Keep relative `--add-dir` paths anchored to the invocation directory. - Load interactive destination configuration before telemetry and login policy initialization, and bind checkout ownership before the first turn. - Reject explicitly untrusted sources, including when destination cloud policy or refreshed configuration reveals distrust. Retain unbound interactive checkouts after startup failure and report manual recovery instructions. ## Testing Add CLI integration and TUI tests for startup and named forks, destination instructions and configuration, ownership before the first request, trust enforcement, and retained-checkout recovery. Extend exec coverage for saved fork directories, explicit `--cd`, relative writable roots, and cloud policy rejection. GitOrigin-RevId: 371583f7feca73b218a4ff188b2b85812c8439e6
This commit is contained in:
committed by
copyberry
parent
52e73e3a54
commit
3525845978
+28
-12
@@ -2486,6 +2486,11 @@ fn reject_unsupported_worktree_for_subcommand(
|
||||
}
|
||||
|
||||
match subcommand {
|
||||
None => Ok(()),
|
||||
Some(Subcommand::Fork(command)) if command.session_id.is_some() && !command.last => Ok(()),
|
||||
Some(Subcommand::Fork(_)) => {
|
||||
anyhow::bail!("`codex fork --worktree` requires an explicit session ID")
|
||||
}
|
||||
Some(Subcommand::Exec(command)) => match &command.command {
|
||||
None | Some(ExecCommand::Fork(_)) => Ok(()),
|
||||
Some(ExecCommand::Resume(_)) => anyhow::bail!(
|
||||
@@ -2496,7 +2501,9 @@ fn reject_unsupported_worktree_for_subcommand(
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
anyhow::bail!("`--worktree` currently supports only `codex exec` and `codex exec fork`")
|
||||
anyhow::bail!(
|
||||
"`--worktree` supports new interactive sessions, `codex fork`, `codex exec`, and `codex exec fork`"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3317,10 +3324,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_worktree_flag_supports_root_local_and_nested_fork_positions() {
|
||||
fn worktree_flag_supports_interactive_exec_and_explicit_fork_positions() {
|
||||
let arguments = [
|
||||
vec!["codex", "--worktree"],
|
||||
vec!["codex", "--worktree", "hello"],
|
||||
vec!["codex", "--worktree", "exec", "hello"],
|
||||
vec!["codex", "exec", "--worktree", "hello"],
|
||||
vec![
|
||||
"codex",
|
||||
"fork",
|
||||
"--worktree",
|
||||
"019f1234-5678-7000-8000-000000000001",
|
||||
],
|
||||
vec![
|
||||
"codex",
|
||||
"exec",
|
||||
@@ -3338,7 +3353,7 @@ mod tests {
|
||||
&cli.subcommand,
|
||||
)
|
||||
.is_ok(),
|
||||
"headless worktree command should be accepted: {arguments:?}",
|
||||
"supported worktree command should be accepted: {arguments:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3346,8 +3361,9 @@ mod tests {
|
||||
#[test]
|
||||
fn worktree_flag_rejects_unsupported_session_and_management_commands() {
|
||||
let arguments = [
|
||||
vec!["codex", "--worktree"],
|
||||
vec!["codex", "--worktree", "login"],
|
||||
vec!["codex", "fork", "--worktree"],
|
||||
vec!["codex", "fork", "--worktree", "--last"],
|
||||
vec!["codex", "exec", "resume", "--worktree", "session"],
|
||||
vec!["codex", "exec", "review", "--worktree"],
|
||||
vec!["codex", "resume", "--worktree", "session"],
|
||||
@@ -3363,17 +3379,17 @@ mod tests {
|
||||
],
|
||||
];
|
||||
|
||||
let mut errors = Vec::new();
|
||||
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:?}",
|
||||
);
|
||||
let error = reject_unsupported_worktree_for_subcommand(
|
||||
cli.interactive.shared.worktree,
|
||||
&cli.subcommand,
|
||||
)
|
||||
.expect_err("unsupported worktree command");
|
||||
errors.push(format!("{}: {error}", arguments.join(" ")));
|
||||
}
|
||||
insta::assert_snapshot!("unsupported_worktree_commands", errors.join("\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: cli/src/main.rs
|
||||
assertion_line: 3324
|
||||
expression: "errors.join(\"\\n\")"
|
||||
---
|
||||
codex --worktree login: `--worktree` supports new interactive sessions, `codex fork`, `codex exec`, and `codex exec fork`
|
||||
codex fork --worktree: `codex fork --worktree` requires an explicit session ID
|
||||
codex fork --worktree --last: `codex fork --worktree` requires an explicit session ID
|
||||
codex exec resume --worktree session: `--worktree` cannot resume an existing session; use `codex exec fork --worktree`
|
||||
codex exec review --worktree: `--worktree` is not supported for code review
|
||||
codex resume --worktree session: `--worktree` supports new interactive sessions, `codex fork`, `codex exec`, and `codex exec fork`
|
||||
codex archive session --worktree: `--worktree` supports new interactive sessions, `codex fork`, `codex exec`, and `codex exec fork`
|
||||
codex queue --thread session --message hi --worktree: `--worktree` supports new interactive sessions, `codex fork`, `codex exec`, and `codex exec fork`
|
||||
Reference in New Issue
Block a user