Include auto-review rollout in feedback uploads (#20064)

## Summary

- include the live auto-review trunk rollout when `/feedback` uploads
logs
- upload that attachment as
`auto-review-rollout-<parent-thread-id>.jsonl` so it is distinguishable
from the parent rollout
- show the same auto-review attachment name in the TUI consent popup

## Scope

- this only covers the live cached auto-review trunk for the current
parent thread
- it does not add durable historical parent->auto-review lookup
- it does not add persisted rollout support for ephemeral parallel
review forks

## UI 

<img width="599" height="185" alt="Screenshot 2026-04-28 at 1 17 18 PM"
src="https://github.com/user-attachments/assets/6a0e79c2-5d21-4702-8a89-f765778bc9e9"
/>

## Validation

- `cargo test -p codex-core
cached_guardian_subagent_exposes_its_rollout_path`
- `cargo test -p codex-feedback`
- `cargo test -p codex-app-server`
- `cargo test -p codex-tui feedback_upload_consent_popup_snapshot`
- `cargo test -p codex-tui
feedback_good_result_consent_popup_includes_connectivity_diagnostics_filename`

## Known unrelated local failures

- `cargo test -p codex-core` currently fails in the pre-existing proxy
env snapshot test
`tools::runtimes::tests::maybe_wrap_shell_lc_with_snapshot_keeps_user_proxy_env_when_proxy_inactive`
- `cargo test -p codex-tui` currently hits pre-existing `status::*`
snapshot drift unrelated to this change

## Follow-Up 
- persist parallel auto-review fork sessions so /feedback can include
their rollout history too
- attach each persisted fork as its own clearly named file, for example
auto-review-rollout-<parent-thread-id>-fork <n>.jsonl, instead of
merging multiple Guardian sessions into one attachment
- keep the same live-session-only scope initially; durable historical
parent -> auto-review lookup can remain a separate decision if we later
need feedback from resumed sessions
This commit is contained in:
Won Park
2026-04-29 11:44:55 -07:00
committed by GitHub
parent 05fd904572
commit 5cf0adba93
10 changed files with 115 additions and 12 deletions

View File

@@ -338,12 +338,18 @@ pub struct FeedbackSnapshot {
pub thread_id: String,
}
pub struct FeedbackAttachmentPath {
pub path: PathBuf,
/// Optional filename to use for the uploaded attachment instead of `path`'s basename.
pub attachment_filename_override: Option<String>,
}
pub struct FeedbackUploadOptions<'a> {
pub classification: &'a str,
pub reason: Option<&'a str>,
pub tags: Option<&'a BTreeMap<String, String>>,
pub include_logs: bool,
pub extra_attachment_paths: &'a [PathBuf],
pub extra_attachment_paths: &'a [FeedbackAttachmentPath],
pub session_source: Option<SessionSource>,
pub logs_override: Option<Vec<u8>>,
}
@@ -501,7 +507,7 @@ impl FeedbackSnapshot {
fn feedback_attachments(
&self,
include_logs: bool,
extra_attachment_paths: &[PathBuf],
extra_attachment_paths: &[FeedbackAttachmentPath],
logs_override: Option<Vec<u8>>,
) -> Vec<sentry::protocol::Attachment> {
use sentry::protocol::Attachment;
@@ -526,22 +532,28 @@ impl FeedbackSnapshot {
});
}
for path in extra_attachment_paths {
let data = match fs::read(path) {
for attachment_path in extra_attachment_paths {
let data = match fs::read(&attachment_path.path) {
Ok(data) => data,
Err(err) => {
tracing::warn!(
path = %path.display(),
path = %attachment_path.path.display(),
error = %err,
"failed to read log attachment; skipping"
);
continue;
}
};
let filename = path
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "extra-log.log".to_string());
let filename = attachment_path
.attachment_filename_override
.clone()
.unwrap_or_else(|| {
attachment_path
.path
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| "extra-log.log".to_string())
});
attachments.push(Attachment {
buffer: data,
filename,
@@ -676,6 +688,10 @@ mod tests {
fn feedback_attachments_gate_connectivity_diagnostics() {
let extra_filename = format!("codex-feedback-extra-{}.jsonl", ThreadId::new());
let extra_path = std::env::temp_dir().join(&extra_filename);
let extra_attachment_path = FeedbackAttachmentPath {
path: extra_path.clone(),
attachment_filename_override: None,
};
fs::write(&extra_path, "rollout").expect("extra attachment should be written");
let snapshot_with_diagnostics = CodexFeedback::new()
@@ -688,7 +704,7 @@ mod tests {
let attachments_with_diagnostics = snapshot_with_diagnostics.feedback_attachments(
/*include_logs*/ true,
std::slice::from_ref(&extra_path),
std::slice::from_ref(&extra_attachment_path),
Some(vec![1]),
);
@@ -715,6 +731,7 @@ mod tests {
);
let attachments_without_diagnostics = CodexFeedback::new()
.snapshot(/*session_id*/ None)
.with_feedback_diagnostics(FeedbackDiagnostics::default())
.feedback_attachments(/*include_logs*/ true, &[], Some(vec![1]));
assert_eq!(