feat(app-server): add optional turn_id to thread/fork (#30277)

## Description

This adds stable optional `turnId` support to `thread/fork`. When
supplied, the fork copies persisted history through that terminal turn,
inclusive, and drops later turns from the new thread.

Omitting or passing `null` preserves the existing full-history fork
behavior, including the interruption marker when the stored source
history ends mid-turn.

## Why

We're deprecating `thread/rollback` and this will help certain UX use
cases work around it by using `thread/fork` + `turn_id` instead.
This commit is contained in:
Owen Lin
2026-06-26 12:35:54 -07:00
committed by GitHub
Unverified
parent 812cd2bb57
commit f72976a5f1
14 changed files with 352 additions and 6 deletions
@@ -330,6 +330,7 @@ use codex_core::path_utils;
#[cfg(test)]
use codex_core::read_head_for_summary;
use codex_core::sandboxing::SandboxPermissions;
use codex_core::truncate_rollout_after_turn_id;
use codex_core::windows_sandbox::WindowsSandboxLevelExt;
use codex_core::windows_sandbox::WindowsSandboxSetupMode as CoreWindowsSandboxSetupMode;
use codex_core::windows_sandbox::WindowsSandboxSetupRequest;
@@ -3387,6 +3387,7 @@ impl ThreadRequestProcessor {
) -> Result<(), JSONRPCErrorError> {
let ThreadForkParams {
thread_id,
last_turn_id,
path,
model,
model_provider,
@@ -3421,12 +3422,20 @@ impl ThreadRequestProcessor {
let history_items = source_thread
.history
.take()
.map(|history| Arc::new(history.items))
.map(|history| history.items)
.ok_or_else(|| {
internal_error(format!(
"thread {source_thread_id} did not include persisted history"
))
})?;
let history_items = if let Some(last_turn_id) = last_turn_id.as_deref() {
Arc::new(
truncate_rollout_after_turn_id(&history_items, last_turn_id)
.map_err(|err| core_thread_write_error("truncate thread for fork", err))?,
)
} else {
Arc::new(history_items)
};
let history_cwd = Some(source_thread.cwd.clone());
// Persist Windows sandbox mode.