From 01ef282efd1c41f07917b71178192dd10972841b Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 15 Jun 2026 16:40:17 +0200 Subject: [PATCH 1/2] Cap feedback upload subtrees Feedback log uploads currently collect the full spawned-thread subtree before querying SQLite logs or resolving rollout attachments. On very long sessions that can pull in hundreds of descendant threads and large rollout files during /feedback.\n\nCap the collected feedback subtree to eight threads so the upload path stays bounded while still including the root session and nearest descendants. --- .../src/request_processors/feedback_processor.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/codex-rs/app-server/src/request_processors/feedback_processor.rs b/codex-rs/app-server/src/request_processors/feedback_processor.rs index 73da67f73596..eab4f183cb0c 100644 --- a/codex-rs/app-server/src/request_processors/feedback_processor.rs +++ b/codex-rs/app-server/src/request_processors/feedback_processor.rs @@ -2,6 +2,8 @@ use super::*; #[cfg(target_os = "windows")] use codex_feedback::WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME; +const MAX_FEEDBACK_TREE_THREADS: usize = 8; + #[derive(Clone)] pub(crate) struct FeedbackRequestProcessor { auth_manager: Arc, @@ -125,6 +127,14 @@ impl FeedbackRequestProcessor { }, None => Vec::new(), }; + let mut feedback_thread_ids = feedback_thread_ids; + let original_len = feedback_thread_ids.len(); + if original_len > MAX_FEEDBACK_TREE_THREADS { + feedback_thread_ids.truncate(MAX_FEEDBACK_TREE_THREADS); + warn!( + "feedback log upload for thread_id={conversation_id:?} truncated from {original_len} threads to {MAX_FEEDBACK_TREE_THREADS}" + ); + } let sqlite_feedback_logs = if let Some(state_db_ctx) = state_db_ctx.as_ref() && !feedback_thread_ids.is_empty() { From 932662bcc1a52e17920a43b10c89f35577c9cb63 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Mon, 15 Jun 2026 16:42:52 +0200 Subject: [PATCH 2/2] Keep newest feedback descendants --- .../request_processors/feedback_processor.rs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/codex-rs/app-server/src/request_processors/feedback_processor.rs b/codex-rs/app-server/src/request_processors/feedback_processor.rs index eab4f183cb0c..3f2c016d53a6 100644 --- a/codex-rs/app-server/src/request_processors/feedback_processor.rs +++ b/codex-rs/app-server/src/request_processors/feedback_processor.rs @@ -129,11 +129,24 @@ impl FeedbackRequestProcessor { }; let mut feedback_thread_ids = feedback_thread_ids; let original_len = feedback_thread_ids.len(); - if original_len > MAX_FEEDBACK_TREE_THREADS { - feedback_thread_ids.truncate(MAX_FEEDBACK_TREE_THREADS); - warn!( - "feedback log upload for thread_id={conversation_id:?} truncated from {original_len} threads to {MAX_FEEDBACK_TREE_THREADS}" - ); + if let Some(conversation_id) = conversation_id { + let mut descendant_thread_ids = feedback_thread_ids + .into_iter() + .filter(|thread_id| *thread_id != conversation_id) + .collect::>(); + // Thread ids are UUIDv7, so lexicographic order tracks creation time. + descendant_thread_ids.sort_unstable_by_key(ToString::to_string); + if original_len > MAX_FEEDBACK_TREE_THREADS { + let keep_descendants = MAX_FEEDBACK_TREE_THREADS.saturating_sub(1); + let split_index = descendant_thread_ids.len().saturating_sub(keep_descendants); + descendant_thread_ids = descendant_thread_ids.split_off(split_index); + warn!( + "feedback log upload for thread_id={conversation_id:?} truncated from {original_len} threads to root plus {keep_descendants} most recent descendants" + ); + } + feedback_thread_ids = Vec::with_capacity(descendant_thread_ids.len() + 1); + feedback_thread_ids.push(conversation_id); + feedback_thread_ids.extend(descendant_thread_ids); } let sqlite_feedback_logs = if let Some(state_db_ctx) = state_db_ctx.as_ref() && !feedback_thread_ids.is_empty()