Avoid config snapshots in live agent subtree traversal (#24057)

## Why
`/feedback` asks `ThreadManager` for the selected agent subtree before
it uploads logs. The previous live subtree path reconstructed
parent-child links by iterating every loaded thread and awaiting each
thread config snapshot, so unrelated loaded-thread state could stall
feedback subtree enumeration.

The loaded-thread set already belongs to
[`ThreadManagerState`](https://github.com/openai/codex/blob/50e6644c9425df2dcbfe52f65fd60bd7f15a8ea2/codex-rs/core/src/thread_manager.rs).
Reading thread-spawn parents from the captured `CodexThread` session
sources at that boundary keeps unload and resume behavior manager-owned
while avoiding per-session config inspection.

## What Changed
- expose parent-child thread-spawn edges for loaded, non-internal
threads from `ThreadManagerState`
- build the live child map from those edges while keeping agent metadata
lookup and ordering in `AgentControl`
- add regression coverage for live subtree enumeration when no state DB
is available

## Validation
- `git diff --check`
- local Rust tests not run per request
This commit is contained in:
jif-oai
2026-05-22 13:06:40 +02:00
committed by GitHub
Unverified
parent 2c6605ab35
commit 5865ec45e5
3 changed files with 83 additions and 12 deletions
+21
View File
@@ -928,6 +928,27 @@ impl ThreadManagerState {
.collect()
}
/// List parent-child edges for currently loaded thread-spawn agents.
pub(crate) async fn list_live_thread_spawn_edges(&self) -> Vec<(ThreadId, ThreadId)> {
self.threads
.read()
.await
.iter()
.filter_map(|(thread_id, thread)| {
if thread.session_source.is_internal() {
return None;
}
match &thread.session_source {
SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id,
..
}) => Some((*parent_thread_id, *thread_id)),
_ => None,
}
})
.collect()
}
/// Fetch a thread by ID or return ThreadNotFound.
pub(crate) async fn get_thread(&self, thread_id: ThreadId) -> CodexResult<Arc<CodexThread>> {
let threads = self.threads.read().await;