[codex] Cap feedback upload subtrees (#28332)

## Summary
- cap feedback log uploads to at most eight threads before SQLite log
aggregation and rollout attachment resolution
- keep the root session included while bounding descendant fanout during
`/feedback` uploads

## Why
Very large sessions can accumulate large spawned-thread subtrees.
Feedback uploads currently walk the entire subtree and then read each
resolved rollout into memory, which can blow up when one session has
hundreds of descendants.

## Validation
- ran `just fmt`
- did not run tests or Clippy per request; CI will cover validation
This commit is contained in:
jif
2026-06-15 15:58:01 +01:00
committed by GitHub
Unverified
parent c8c78b63a7
commit b3f6f70b68
@@ -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<AuthManager>,
@@ -125,6 +127,27 @@ impl FeedbackRequestProcessor {
},
None => Vec::new(),
};
let mut feedback_thread_ids = feedback_thread_ids;
let original_len = feedback_thread_ids.len();
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::<Vec<_>>();
// 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()
{