mirror of
https://github.com/openai/codex.git
synced 2026-04-29 00:55:38 +00:00
fix: address flakiness in thread_resume_rejoins_running_thread_even_with_override_mismatch (#12381)
## Why `thread/resume` responses for already-running threads can be reported as `Idle` even while a turn is still in progress. This is caused by a timing window where the runtime watch state has not yet observed the running-thread transition, so API clients can receive stale status information at resume time. Possibly related: https://github.com/openai/codex/pull/11786 ## What - Add a shared status normalization helper, `resolve_thread_status`, in `codex-rs/app-server/src/thread_status.rs` that resolves `Idle`/`NotLoaded` to `Active { active_flags: [] }` when an in-progress turn is known. - Reuse this helper across thread response paths in `codex-rs/app-server/src/codex_message_processor.rs` (including `thread/start`, `thread/unarchive`, `thread/read`, `thread/resume`, `thread/fork`, and review/thread-started notification responses). - In `handle_pending_thread_resume_request`, use both the in-memory `active_turn_snapshot` and the resumed rollout turns to decide whether a turn is in progress before resolving thread status for the response. - Extend `thread_status` tests to validate the new status-resolution behavior directly. ## Verification - `cargo test -p codex-app-server suite::v2::thread_resume::thread_resume_rejoins_running_thread_even_with_override_mismatch`
This commit is contained in:
@@ -239,6 +239,22 @@ impl ThreadWatchManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_thread_status(
|
||||
status: ThreadStatus,
|
||||
has_in_progress_turn: bool,
|
||||
) -> ThreadStatus {
|
||||
// Running-turn events can arrive before the watch runtime state is observed by
|
||||
// the listener loop. In that window we prefer to reflect a real active turn as
|
||||
// `Active` instead of `Idle`/`NotLoaded`.
|
||||
if has_in_progress_turn && matches!(status, ThreadStatus::Idle | ThreadStatus::NotLoaded) {
|
||||
return ThreadStatus::Active {
|
||||
active_flags: Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
status
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ThreadWatchState {
|
||||
runtime_by_thread_id: HashMap<String, RuntimeFacts>,
|
||||
@@ -459,6 +475,37 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolves_in_progress_turn_to_active_status() {
|
||||
let status = resolve_thread_status(ThreadStatus::Idle, true);
|
||||
assert_eq!(
|
||||
status,
|
||||
ThreadStatus::Active {
|
||||
active_flags: Vec::new(),
|
||||
}
|
||||
);
|
||||
|
||||
let status = resolve_thread_status(ThreadStatus::NotLoaded, true);
|
||||
assert_eq!(
|
||||
status,
|
||||
ThreadStatus::Active {
|
||||
active_flags: Vec::new(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_status_when_no_in_progress_turn() {
|
||||
assert_eq!(
|
||||
resolve_thread_status(ThreadStatus::Idle, false),
|
||||
ThreadStatus::Idle
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_thread_status(ThreadStatus::SystemError, false),
|
||||
ThreadStatus::SystemError
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn system_error_sets_idle_flag_until_next_turn() {
|
||||
let manager = ThreadWatchManager::new();
|
||||
|
||||
Reference in New Issue
Block a user