Limit rollout metadata reads to headers (#33892)

## Why

Callers that only need session metadata should not scan an entire rollout or fail on an unreadable tail after valid metadata.

## What changed

- Read session metadata directly from the rollout line reader, stopping as soon as it is found for both plain and compressed rollouts while preserving pre-header and validation behavior.
- Bound the doctor thread inventory scan to the first 64 non-empty header lines. Prefer the metadata thread ID and retain the validated filename fallback for legacy rollouts without metadata.

## Testing

- Cover invalid UTF-8 tails, compressed rollouts, pre-header records, metadata and filename ID mismatches, the scan boundary, legacy fallback, and unusable headers.

GitOrigin-RevId: 49576bd2877942e64082ce5ed057ecc848c025ec
This commit is contained in:
rtaylor-oai authored and copyberry committed 2026-07-17 19:20:08 +00:00
1 parent f24e695470
commit 24e9b849fa
3 files changed
+486 -27

No files matched your search

+31 -13
View File
@@ -1278,19 +1278,37 @@ fn event_msg_preview(event: &EventMsg) -> Option<String> {
/// Read the SessionMetaLine from the head of a rollout file for reuse by
/// callers that need the session metadata (e.g. to derive a cwd for config).
pub async fn read_session_meta_line(path: &Path) -> io::Result<SessionMetaLine> {
let head = read_head_for_summary(path).await?;
let Some(first) = head.first() else {
return Err(io::Error::other(format!(
"rollout at {} is empty",
path.display()
)));
};
serde_json::from_value::<SessionMetaLine>(first.clone()).map_err(|_| {
io::Error::other(format!(
"rollout at {} does not start with session metadata",
path.display()
))
})
let mut lines = compression::open_rollout_line_reader(path).await?;
while let Some(line) = lines.next_line().await? {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let Ok(rollout_line) = serde_json::from_str::<RolloutLine>(trimmed) else {
if let Ok(value) = serde_json::from_str::<serde_json::Value>(trimmed) {
crate::recorder::reject_unknown_thread_history_mode(&value)?;
}
continue;
};
match rollout_line.item {
RolloutItem::SessionMeta(session_meta_line) => return Ok(session_meta_line),
RolloutItem::ResponseItem(_) | RolloutItem::InterAgentCommunication(_) => {
return Err(io::Error::other(format!(
"rollout at {} does not start with session metadata",
path.display()
)));
}
RolloutItem::InterAgentCommunicationMetadata { .. }
| RolloutItem::Compacted(_)
| RolloutItem::TurnContext(_)
| RolloutItem::WorldState(_)
| RolloutItem::EventMsg(_) => {}
}
}
Err(io::Error::other(format!(
"rollout at {} is empty",
path.display()
)))
}
async fn file_modified_time(path: &Path) -> io::Result<Option<OffsetDateTime>> {