Attach Windows sandbox log to feedback reports (#24623)

## Why

Windows sandbox diagnostics are currently hard to recover from
`/feedback` even though they are often the most useful artifact when
debugging sandbox behavior. Now that sandbox logging uses daily rolling
files, feedback can safely include the current day's sandbox log without
uploading the old ever-growing legacy `sandbox.log`.

## What changed

- Add a `codex-windows-sandbox` helper that resolves the current daily
sandbox log from `codex_home`.
- When feedback is submitted with logs enabled on Windows, app-server
attaches today's sandbox log if it exists.
- Upload the attachment under the stable filename `windows-sandbox.log`,
independent of the dated on-disk filename.
- Keep existing raw `extra_log_files` behavior unchanged for rollout and
desktop log attachments.

## Verification

- `cargo fmt -p codex-app-server -p codex-windows-sandbox`
- `cargo test -p codex-windows-sandbox
current_log_file_path_for_codex_home_uses_sandbox_dir`
- `cargo test -p codex-app-server
windows_sandbox_log_attachment_uses_current_log`
- Manual CLI/TUI `/feedback` test confirmed Sentry received
`windows-sandbox.log`.
This commit is contained in:
iceweasel-oai
2026-05-26 15:59:25 -07:00
committed by GitHub
Unverified
parent 46391f7efa
commit 9826581e7b
12 changed files with 131 additions and 1 deletions
+1
View File
@@ -1929,6 +1929,7 @@ dependencies = [
"codex-utils-json-to-toml",
"codex-utils-pty",
"codex-web-search-extension",
"codex-windows-sandbox",
"core_test_support",
"flate2",
"futures",
+3
View File
@@ -94,6 +94,9 @@ tracing = { workspace = true, features = ["log"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "fmt", "json"] }
uuid = { workspace = true, features = ["serde", "v7"] }
[target.'cfg(windows)'.dependencies]
codex-windows-sandbox = { workspace = true }
[dev-dependencies]
app_test_support = { workspace = true }
axum = { workspace = true, default-features = false, features = [
@@ -1,4 +1,6 @@
use super::*;
#[cfg(target_os = "windows")]
use codex_feedback::WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME;
#[derive(Clone)]
pub(crate) struct FeedbackRequestProcessor {
@@ -186,6 +188,12 @@ impl FeedbackRequestProcessor {
)),
});
}
if let Some(sandbox_log_attachment) =
windows_sandbox_log_attachment(&self.config.codex_home)
&& seen_attachment_paths.insert(sandbox_log_attachment.path.clone())
{
attachment_paths.push(sandbox_log_attachment);
}
}
if let Some(extra_log_files) = extra_log_files {
for extra_log_file in extra_log_files {
@@ -264,3 +272,46 @@ impl FeedbackRequestProcessor {
fn auto_review_rollout_filename(thread_id: ThreadId) -> String {
format!("auto-review-rollout-{thread_id}.jsonl")
}
#[cfg(target_os = "windows")]
fn windows_sandbox_log_attachment(codex_home: &Path) -> Option<FeedbackAttachmentPath> {
let sandbox_log_path = codex_windows_sandbox::current_log_file_path_for_codex_home(codex_home);
sandbox_log_path
.is_file()
.then_some(FeedbackAttachmentPath {
path: sandbox_log_path,
attachment_filename_override: Some(WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME.to_string()),
})
}
#[cfg(not(target_os = "windows"))]
fn windows_sandbox_log_attachment(_codex_home: &Path) -> Option<FeedbackAttachmentPath> {
None
}
#[cfg(all(test, target_os = "windows"))]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn windows_sandbox_log_attachment_uses_current_log() {
let codex_home = tempfile::tempdir().expect("create tempdir");
let sandbox_dir = codex_windows_sandbox::sandbox_dir(codex_home.path());
std::fs::create_dir_all(&sandbox_dir).expect("create sandbox dir");
let sandbox_log_path =
codex_windows_sandbox::current_log_file_path_for_codex_home(codex_home.path());
std::fs::write(&sandbox_log_path, "sandbox log").expect("write sandbox log");
let attachment = windows_sandbox_log_attachment(codex_home.path())
.map(|attachment| (attachment.path, attachment.attachment_filename_override));
assert_eq!(
attachment,
Some((
sandbox_log_path,
Some(WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME.to_string())
))
);
}
}
+2
View File
@@ -29,6 +29,8 @@ pub use feedback_diagnostics::FeedbackDiagnostics;
/// Filename used for the redacted `codex doctor --json` feedback attachment.
pub const DOCTOR_REPORT_ATTACHMENT_FILENAME: &str = "codex-doctor-report.json";
/// Filename used for the Windows sandbox log feedback attachment.
pub const WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME: &str = "windows-sandbox.log";
const DEFAULT_MAX_BYTES: usize = 4 * 1024 * 1024; // 4 MiB
const SENTRY_DSN: &str =
"https://ae32ed50620d7a7792c1ce5df38b3e3e@o33249.ingest.us.sentry.io/4510195390611458";
@@ -1,6 +1,7 @@
use codex_feedback::DOCTOR_REPORT_ATTACHMENT_FILENAME;
use codex_feedback::FEEDBACK_DIAGNOSTICS_ATTACHMENT_FILENAME;
use codex_feedback::FeedbackDiagnostics;
use codex_feedback::WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyModifiers;
@@ -472,6 +473,7 @@ pub(crate) fn feedback_upload_consent_params(
category: FeedbackCategory,
rollout_path: Option<std::path::PathBuf>,
auto_review_rollout_filename: Option<String>,
include_windows_sandbox_log: bool,
feedback_diagnostics: &FeedbackDiagnostics,
) -> super::SelectionViewParams {
use super::popup_consts::standard_popup_hint_line;
@@ -509,6 +511,15 @@ pub(crate) fn feedback_upload_consent_params(
])
.into(),
];
if include_windows_sandbox_log {
header_lines.push(
Line::from(vec![
"".into(),
WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME.into(),
])
.into(),
);
}
if let Some(path) = rollout_path.as_deref()
&& let Some(name) = path.file_name().map(|s| s.to_string_lossy().to_string())
{
@@ -696,6 +707,7 @@ mod tests {
FeedbackCategory::Bug,
Some(std::path::PathBuf::from("rollout.jsonl")),
Some("auto-review-rollout.jsonl".to_string()),
/*include_windows_sandbox_log*/ false,
&FeedbackDiagnostics::default(),
);
@@ -704,6 +716,27 @@ mod tests {
insta::assert_snapshot!("feedback_upload_consent_lists_doctor_report", rendered);
}
#[test]
fn feedback_upload_consent_lists_windows_sandbox_log_when_included() {
let (tx_raw, _rx) = tokio::sync::mpsc::unbounded_channel::<AppEvent>();
let tx = AppEventSender::new(tx_raw);
let params = feedback_upload_consent_params(
tx,
FeedbackCategory::Bug,
Some(std::path::PathBuf::from("rollout.jsonl")),
Some("auto-review-rollout.jsonl".to_string()),
/*include_windows_sandbox_log*/ true,
&FeedbackDiagnostics::default(),
);
let rendered = render_renderable(params.header.as_ref(), /*width*/ 60);
insta::assert_snapshot!(
"feedback_upload_consent_lists_windows_sandbox_log_when_included",
rendered
);
}
#[test]
fn submit_feedback_emits_submit_event_with_trimmed_note() {
let (tx_raw, mut rx) = tokio::sync::mpsc::unbounded_channel::<AppEvent>();
@@ -1,6 +1,6 @@
---
source: tui/src/bottom_pane/feedback_view.rs
assertion_line: 704
assertion_line: 716
expression: rendered
---
Upload logs?
@@ -0,0 +1,13 @@
---
source: tui/src/bottom_pane/feedback_view.rs
assertion_line: 734
expression: rendered
---
Upload logs?
The following files will be sent:
• codex-logs.log
• codex-doctor-report.json
• windows-sandbox.log
• rollout.jsonl
• auto-review-rollout.jsonl
+7
View File
@@ -977,12 +977,19 @@ impl ChatWidget {
pub(crate) fn open_feedback_consent(&mut self, category: crate::app_event::FeedbackCategory) {
let snapshot = self.feedback.snapshot(self.thread_id);
#[cfg(target_os = "windows")]
let include_windows_sandbox_log =
codex_windows_sandbox::current_log_file_path_for_codex_home(&self.config.codex_home)
.is_file();
#[cfg(not(target_os = "windows"))]
let include_windows_sandbox_log = false;
let params = crate::bottom_pane::feedback_upload_consent_params(
self.app_event_tx.clone(),
category,
self.current_rollout_path.clone(),
self.thread_id
.map(|thread_id| format!("auto-review-rollout-{thread_id}.jsonl")),
include_windows_sandbox_log,
snapshot.feedback_diagnostics(),
);
self.bottom_pane.show_selection_view(params);
@@ -1,5 +1,6 @@
---
source: tui/src/chatwidget/tests/popups_and_settings.rs
assertion_line: 2562
expression: popup
---
Upload logs?
@@ -7,6 +8,7 @@ expression: popup
The following files will be sent:
• codex-logs.log
• codex-doctor-report.json
• windows-sandbox.log
• auto-review-rollout-thread-1.jsonl
• codex-connectivity-diagnostics.txt
@@ -2621,6 +2621,7 @@ async fn feedback_upload_consent_popup_snapshot() {
crate::app_event::FeedbackCategory::Bug,
chat.current_rollout_path.clone(),
Some("auto-review-rollout-thread-1.jsonl".to_string()),
/*include_windows_sandbox_log*/ true,
&codex_feedback::FeedbackDiagnostics::new(vec![codex_feedback::FeedbackDiagnostic {
headline: "Proxy environment variables are set and may affect connectivity."
.to_string(),
@@ -2641,6 +2642,7 @@ async fn feedback_good_result_consent_popup_includes_connectivity_diagnostics_fi
crate::app_event::FeedbackCategory::GoodResult,
chat.current_rollout_path.clone(),
Some("auto-review-rollout-thread-1.jsonl".to_string()),
/*include_windows_sandbox_log*/ false,
&codex_feedback::FeedbackDiagnostics::new(vec![codex_feedback::FeedbackDiagnostic {
headline: "Proxy environment variables are set and may affect connectivity."
.to_string(),
+2
View File
@@ -177,6 +177,8 @@ pub use ipc_framed::write_frame;
#[cfg(target_os = "windows")]
pub use logging::current_log_file_path;
#[cfg(target_os = "windows")]
pub use logging::current_log_file_path_for_codex_home;
#[cfg(target_os = "windows")]
pub use logging::log_file_path_for_utc_date;
#[cfg(target_os = "windows")]
pub use logging::log_note;
@@ -43,6 +43,10 @@ pub fn current_log_file_path(base_dir: &Path) -> PathBuf {
log_file_path_for_utc_date(base_dir, chrono::Utc::now().date_naive())
}
pub fn current_log_file_path_for_codex_home(codex_home: &Path) -> PathBuf {
current_log_file_path(&crate::sandbox_dir(codex_home))
}
pub fn log_writer(base_dir: &Path) -> Option<RollingFileAppender> {
if !base_dir.is_dir() {
return None;
@@ -142,4 +146,14 @@ mod tests {
PathBuf::from("logs").join("sandbox.2026-05-21.log")
);
}
#[test]
fn current_log_file_path_for_codex_home_uses_sandbox_dir() {
let codex_home = Path::new("codex-home");
assert_eq!(
current_log_file_path_for_codex_home(codex_home),
current_log_file_path(&codex_home.join(".sandbox"))
);
}
}