mirror of
https://github.com/openai/codex.git
synced 2026-11-13 22:47:20 +00:00
Respect resume cwd overrides for idle cached threads (#24528)
Fixes #24186. ## Why When the TUI resumes a thread through the local app-server daemon with a selected workspace, `thread/resume` can hit an already-loaded but idle cached thread. That path previously rejoined the cached `CodexThread`, so cwd/config overrides in `ThreadResumeParams` were ignored and the resumed session kept using the old cwd. ## What changed App-server now treats a loaded-but-idle thread with no subscribers as a cache entry when resume overrides differ: it unloads that cached thread and lets the normal resume path rebuild it with the requested cwd/config. Threads that still have subscribers, or active runtime work, continue to rejoin the existing loaded thread so in-flight state remains observable. The existing thread teardown helper was generalized from archive-specific cleanup to shared unload cleanup for this path.
This commit is contained in:
1 parent
3da89d4831
commit
5cd9b8086a
3 files changed
+87
-19
No files matched your search
@@ -2787,15 +2787,20 @@ impl ThreadRequestProcessor {
|
||||
/*include_history*/ true,
|
||||
)
|
||||
.await?;
|
||||
if source_thread.thread_id != existing_thread_id {
|
||||
return Err(invalid_request(format!(
|
||||
"cannot resume running thread {existing_thread_id} from source thread {}",
|
||||
source_thread.thread_id
|
||||
)));
|
||||
}
|
||||
Some((existing_thread_id, existing_thread, source_thread))
|
||||
} else {
|
||||
None
|
||||
let source_thread = self
|
||||
.read_stored_thread_for_resume(
|
||||
¶ms.thread_id,
|
||||
params.path.as_ref(),
|
||||
/*include_history*/ true,
|
||||
)
|
||||
.await?;
|
||||
let existing_thread_id = source_thread.thread_id;
|
||||
match self.thread_manager.get_thread(existing_thread_id).await {
|
||||
Ok(existing_thread) => Some((existing_thread_id, existing_thread, source_thread)),
|
||||
Err(_) => None,
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((existing_thread_id, existing_thread, source_thread)) = running_thread {
|
||||
@@ -2812,6 +2817,48 @@ impl ThreadRequestProcessor {
|
||||
active_path.display()
|
||||
)));
|
||||
}
|
||||
let config_snapshot = existing_thread.config_snapshot().await;
|
||||
let mismatch_details = collect_resume_override_mismatches(params, &config_snapshot);
|
||||
if !mismatch_details.is_empty() {
|
||||
let has_subscribers = !self
|
||||
.thread_state_manager
|
||||
.subscribed_connection_ids(existing_thread_id)
|
||||
.await
|
||||
.is_empty();
|
||||
let loaded_status = self
|
||||
.thread_watch_manager
|
||||
.loaded_status_for_thread(&existing_thread_id.to_string())
|
||||
.await;
|
||||
let is_running =
|
||||
matches!(existing_thread.agent_status().await, AgentStatus::Running);
|
||||
|
||||
if !has_subscribers && matches!(loaded_status, ThreadStatus::Idle) && !is_running {
|
||||
// A loaded idle thread is only a cache entry. Shut it down
|
||||
// before removing it so cold resume cannot duplicate a
|
||||
// thread that timed out during shutdown.
|
||||
match wait_for_thread_shutdown(&existing_thread).await {
|
||||
ThreadShutdownResult::Complete => {
|
||||
self.thread_manager.remove_thread(&existing_thread_id).await;
|
||||
self.finalize_thread_teardown(existing_thread_id).await;
|
||||
return Ok(false);
|
||||
}
|
||||
ThreadShutdownResult::SubmitFailed => {
|
||||
warn!("failed to submit Shutdown to thread {existing_thread_id}");
|
||||
}
|
||||
ThreadShutdownResult::TimedOut => {
|
||||
warn!("thread {existing_thread_id} shutdown timed out");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve rejoin semantics when another client can still observe
|
||||
// the loaded thread or shutdown did not complete.
|
||||
tracing::warn!(
|
||||
"thread/resume overrides ignored for loaded thread {}: {}",
|
||||
existing_thread_id,
|
||||
mismatch_details.join("; ")
|
||||
);
|
||||
}
|
||||
let redact_resume_payloads =
|
||||
should_redact_thread_resume_payloads(app_server_client_name.as_deref());
|
||||
let history_items = source_thread
|
||||
@@ -2841,15 +2888,6 @@ impl ThreadRequestProcessor {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let config_snapshot = existing_thread.config_snapshot().await;
|
||||
let mismatch_details = collect_resume_override_mismatches(params, &config_snapshot);
|
||||
if !mismatch_details.is_empty() {
|
||||
tracing::warn!(
|
||||
"thread/resume overrides ignored for running thread {}: {}",
|
||||
existing_thread_id,
|
||||
mismatch_details.join("; ")
|
||||
);
|
||||
}
|
||||
let mut summary_source_thread = source_thread;
|
||||
summary_source_thread.history = None;
|
||||
let mut thread_summary = self.stored_thread_to_api_thread(
|
||||
|
||||
Reference in new issue
Block a user