Attach connector caches to diagnostic uploads (#32891)

## What changed

- Include the active Codex Apps tools cache and connector directory cache, when available, in diagnostic uploads that include logs.
- Resolve cache paths from the current authentication context and use stable attachment filenames.
- List both optional cache files in the upload consent dialog.

## Testing

- Add coverage for selecting existing active cache files, including a directory cache without an account ID.
- Update consent dialog snapshots to include the optional attachments.

GitOrigin-RevId: 1d4ffbf2bcb96653f03dc25f91d80dd65038d585
This commit is contained in:
stevenlee-oai authored and copyberry committed 2026-07-13 22:50:28 +00:00
1 parent bb947e8e36
commit fb350d1e7d
10 files changed
+197 -13

No files matched your search

@@ -1,4 +1,9 @@
use super::*; use super::*;
use codex_connectors::ConnectorDirectoryCacheContext;
use codex_connectors::ConnectorDirectoryCacheKey;
use codex_connectors::connector_runtime_cache_path;
use codex_feedback::CODEX_APP_DIRECTORY_CACHE_ATTACHMENT_FILENAME;
use codex_feedback::CODEX_APPS_TOOLS_CACHE_ATTACHMENT_FILENAME;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
use codex_feedback::WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME; use codex_feedback::WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME;
@@ -70,17 +75,16 @@ impl FeedbackRequestProcessor {
None => None, None => None,
}; };
if let Some(chatgpt_user_id) = self let auth = self.auth_manager.auth_cached();
.auth_manager if let Some(chatgpt_user_id) = auth
.auth_cached() .as_ref()
.and_then(|auth| auth.get_chatgpt_user_id()) .and_then(codex_login::CodexAuth::get_chatgpt_user_id)
{ {
tracing::info!(target: "feedback_tags", chatgpt_user_id); tracing::info!(target: "feedback_tags", chatgpt_user_id);
} }
if let Some(account_id) = self if let Some(account_id) = auth
.auth_manager .as_ref()
.auth_cached() .and_then(codex_login::CodexAuth::get_account_id)
.and_then(|auth| auth.get_account_id())
{ {
tracing::info!(target: "feedback_tags", account_id); tracing::info!(target: "feedback_tags", account_id);
} }
@@ -197,6 +201,15 @@ impl FeedbackRequestProcessor {
{ {
attachment_paths.push(sandbox_log_attachment); attachment_paths.push(sandbox_log_attachment);
} }
for cache_attachment in tool_cache_feedback_attachments(
self.config.codex_home.as_path(),
&self.config.chatgpt_base_url,
auth.as_ref(),
) {
if seen_attachment_paths.insert(cache_attachment.path.clone()) {
attachment_paths.push(cache_attachment);
}
}
} }
if let Some(extra_log_files) = extra_log_files { if let Some(extra_log_files) = extra_log_files {
for extra_log_file in extra_log_files { for extra_log_file in extra_log_files {
@@ -272,6 +285,47 @@ impl FeedbackRequestProcessor {
} }
} }
fn tool_cache_feedback_attachments(
codex_home: &Path,
chatgpt_base_url: &str,
auth: Option<&CodexAuth>,
) -> Vec<FeedbackAttachmentPath> {
let mut attachments = Vec::with_capacity(2);
let tools_cache_path = connector_runtime_cache_path(codex_home, auth);
if tools_cache_path.is_file() {
attachments.push(FeedbackAttachmentPath {
path: tools_cache_path,
attachment_filename_override: Some(
CODEX_APPS_TOOLS_CACHE_ATTACHMENT_FILENAME.to_string(),
),
});
}
let Some(auth) = auth.filter(|auth| auth.uses_codex_backend()) else {
return attachments;
};
let directory_cache_context = ConnectorDirectoryCacheContext::new(
codex_home.to_path_buf(),
ConnectorDirectoryCacheKey::new(
chatgpt_base_url.to_string(),
auth.get_account_id(),
auth.get_chatgpt_user_id(),
auth.is_workspace_account(),
),
);
let directory_cache_path = directory_cache_context.cache_path();
if directory_cache_path.is_file() {
attachments.push(FeedbackAttachmentPath {
path: directory_cache_path,
attachment_filename_override: Some(
CODEX_APP_DIRECTORY_CACHE_ATTACHMENT_FILENAME.to_string(),
),
});
}
attachments
}
fn auto_review_rollout_filename(thread_id: ThreadId) -> String { fn auto_review_rollout_filename(thread_id: ThreadId) -> String {
format!("auto-review-rollout-{thread_id}.jsonl") format!("auto-review-rollout-{thread_id}.jsonl")
} }
@@ -292,11 +346,106 @@ fn windows_sandbox_log_attachment(_codex_home: &Path) -> Option<FeedbackAttachme
None None
} }
#[cfg(all(test, target_os = "windows"))] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
#[test]
fn tool_cache_feedback_attachments_include_existing_active_cache_files() {
let codex_home = tempfile::tempdir().expect("create tempdir");
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let tools_cache_path = connector_runtime_cache_path(codex_home.path(), Some(&auth));
std::fs::create_dir_all(tools_cache_path.parent().expect("tools cache parent"))
.expect("create tools cache directory");
std::fs::write(&tools_cache_path, b"tools").expect("write tools cache");
let account_id = auth.get_account_id().expect("dummy auth account id");
let directory_cache_context = ConnectorDirectoryCacheContext::new(
codex_home.path().to_path_buf(),
ConnectorDirectoryCacheKey::new(
"https://chatgpt.com/backend-api".to_string(),
Some(account_id),
auth.get_chatgpt_user_id(),
auth.is_workspace_account(),
),
);
let directory_cache_path = directory_cache_context.cache_path();
std::fs::create_dir_all(
directory_cache_path
.parent()
.expect("directory cache parent"),
)
.expect("create directory cache directory");
std::fs::write(&directory_cache_path, b"directory").expect("write directory cache");
let attachments = tool_cache_feedback_attachments(
codex_home.path(),
"https://chatgpt.com/backend-api",
Some(&auth),
)
.into_iter()
.map(|attachment| (attachment.path, attachment.attachment_filename_override))
.collect::<Vec<_>>();
assert_eq!(
attachments,
vec![
(
tools_cache_path,
Some(CODEX_APPS_TOOLS_CACHE_ATTACHMENT_FILENAME.to_string()),
),
(
directory_cache_path,
Some(CODEX_APP_DIRECTORY_CACHE_ATTACHMENT_FILENAME.to_string()),
),
]
);
}
#[test]
fn tool_cache_feedback_attachments_include_directory_cache_without_account_id() {
let codex_home = tempfile::tempdir().expect("create tempdir");
let auth = CodexAuth::Headers(codex_login::AuthHeaders::new(
reqwest::header::HeaderMap::new(),
));
let directory_cache_context = ConnectorDirectoryCacheContext::new(
codex_home.path().to_path_buf(),
ConnectorDirectoryCacheKey::new(
"https://chatgpt.com/backend-api".to_string(),
/*account_id*/ None,
auth.get_chatgpt_user_id(),
auth.is_workspace_account(),
),
);
let directory_cache_path = directory_cache_context.cache_path();
std::fs::create_dir_all(
directory_cache_path
.parent()
.expect("directory cache parent"),
)
.expect("create directory cache directory");
std::fs::write(&directory_cache_path, b"directory").expect("write directory cache");
let attachments = tool_cache_feedback_attachments(
codex_home.path(),
"https://chatgpt.com/backend-api",
Some(&auth),
)
.into_iter()
.map(|attachment| (attachment.path, attachment.attachment_filename_override))
.collect::<Vec<_>>();
assert_eq!(
attachments,
vec![(
directory_cache_path,
Some(CODEX_APP_DIRECTORY_CACHE_ATTACHMENT_FILENAME.to_string()),
)]
);
}
#[cfg(target_os = "windows")]
#[test] #[test]
fn windows_sandbox_log_attachment_uses_current_log() { fn windows_sandbox_log_attachment_uses_current_log() {
let codex_home = tempfile::tempdir().expect("create tempdir"); let codex_home = tempfile::tempdir().expect("create tempdir");
@@ -6,6 +6,7 @@
//! owned by the connector metadata store, not by this module. //! owned by the connector metadata store, not by this module.
use std::collections::HashMap; use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
@@ -75,6 +76,15 @@ pub fn connector_runtime_context_key(auth: Option<&CodexAuth>) -> ConnectorRunti
} }
} }
/// Returns the persisted connector runtime tools cache path for the active auth identity.
pub fn connector_runtime_cache_path(codex_home: &Path, auth: Option<&CodexAuth>) -> PathBuf {
let identity = ConnectorRuntimeIdentity {
codex_home: codex_home.to_path_buf(),
key: connector_runtime_context_key(auth),
};
tools_cache_path(&identity)
}
/// One atomically published connector runtime state. /// One atomically published connector runtime state.
/// ///
/// Tools remain raw and in response order. Local and managed configuration is /// Tools remain raw and in response order. Local and managed configuration is
+2 -1
View File
@@ -26,7 +26,8 @@ impl ConnectorDirectoryCacheContext {
} }
} }
pub(crate) fn cache_path(&self) -> PathBuf { /// Returns the persisted connector directory cache path for this identity.
pub fn cache_path(&self) -> PathBuf {
let cache_key_json = serde_json::to_string(&self.cache_key).unwrap_or_default(); let cache_key_json = serde_json::to_string(&self.cache_key).unwrap_or_default();
let cache_key_hash = sha1_hex(&cache_key_json); let cache_key_hash = sha1_hex(&cache_key_json);
self.codex_home self.codex_home
+1
View File
@@ -36,6 +36,7 @@ pub use connector_runtime::ConnectorRuntimeFetchTicket;
pub use connector_runtime::ConnectorRuntimeManager; pub use connector_runtime::ConnectorRuntimeManager;
pub use connector_runtime::ConnectorRuntimePayload; pub use connector_runtime::ConnectorRuntimePayload;
pub use connector_runtime::ConnectorRuntimeSnapshot; pub use connector_runtime::ConnectorRuntimeSnapshot;
pub use connector_runtime::connector_runtime_cache_path;
pub use connector_runtime::connector_runtime_context_key; pub use connector_runtime::connector_runtime_context_key;
pub use directory_cache::ConnectorDirectoryCacheContext; pub use directory_cache::ConnectorDirectoryCacheContext;
pub use plugin_config::parse_plugin_app_config; pub use plugin_config::parse_plugin_app_config;
+4
View File
@@ -31,6 +31,10 @@ pub use feedback_diagnostics::FeedbackDiagnostics;
/// Filename used for the redacted `codex doctor --json` feedback attachment. /// Filename used for the redacted `codex doctor --json` feedback attachment.
pub const DOCTOR_REPORT_ATTACHMENT_FILENAME: &str = "codex-doctor-report.json"; pub const DOCTOR_REPORT_ATTACHMENT_FILENAME: &str = "codex-doctor-report.json";
/// Filename used for the raw Codex Apps MCP tools cache feedback attachment.
pub const CODEX_APPS_TOOLS_CACHE_ATTACHMENT_FILENAME: &str = "codex-apps-tools-cache.json";
/// Filename used for the raw connector directory cache feedback attachment.
pub const CODEX_APP_DIRECTORY_CACHE_ATTACHMENT_FILENAME: &str = "codex-app-directory-cache.json";
/// Filename used for the Windows sandbox log feedback attachment. /// Filename used for the Windows sandbox log feedback attachment.
pub const WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME: &str = "windows-sandbox.log"; pub const WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME: &str = "windows-sandbox.log";
const DEFAULT_MAX_BYTES: usize = 4 * 1024 * 1024; // 4 MiB const DEFAULT_MAX_BYTES: usize = 4 * 1024 * 1024; // 4 MiB
@@ -1,3 +1,5 @@
use codex_feedback::CODEX_APP_DIRECTORY_CACHE_ATTACHMENT_FILENAME;
use codex_feedback::CODEX_APPS_TOOLS_CACHE_ATTACHMENT_FILENAME;
use codex_feedback::DOCTOR_REPORT_ATTACHMENT_FILENAME; use codex_feedback::DOCTOR_REPORT_ATTACHMENT_FILENAME;
use codex_feedback::FEEDBACK_DIAGNOSTICS_ATTACHMENT_FILENAME; use codex_feedback::FEEDBACK_DIAGNOSTICS_ATTACHMENT_FILENAME;
use codex_feedback::FeedbackDiagnostics; use codex_feedback::FeedbackDiagnostics;
@@ -510,6 +512,16 @@ pub(crate) fn feedback_upload_consent_params(
DOCTOR_REPORT_ATTACHMENT_FILENAME.into(), DOCTOR_REPORT_ATTACHMENT_FILENAME.into(),
]) ])
.into(), .into(),
Line::from(vec![
" • ".into(),
format!("{CODEX_APPS_TOOLS_CACHE_ATTACHMENT_FILENAME} (if available)").into(),
])
.into(),
Line::from(vec![
" • ".into(),
format!("{CODEX_APP_DIRECTORY_CACHE_ATTACHMENT_FILENAME} (if available)").into(),
])
.into(),
]; ];
if include_windows_sandbox_log { if include_windows_sandbox_log {
header_lines.push( header_lines.push(
@@ -1,6 +1,6 @@
--- ---
source: tui/src/bottom_pane/feedback_view.rs source: tui/src/bottom_pane/feedback_view.rs
assertion_line: 716 assertion_line: 728
expression: rendered expression: rendered
--- ---
Upload logs? Upload logs?
@@ -8,5 +8,7 @@ Upload logs?
The following files will be sent: The following files will be sent:
• codex-logs.log • codex-logs.log
• codex-doctor-report.json • codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• rollout.jsonl • rollout.jsonl
• auto-review-rollout.jsonl • auto-review-rollout.jsonl
@@ -1,6 +1,6 @@
--- ---
source: tui/src/bottom_pane/feedback_view.rs source: tui/src/bottom_pane/feedback_view.rs
assertion_line: 734 assertion_line: 746
expression: rendered expression: rendered
--- ---
Upload logs? Upload logs?
@@ -8,6 +8,8 @@ Upload logs?
The following files will be sent: The following files will be sent:
• codex-logs.log • codex-logs.log
• codex-doctor-report.json • codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• windows-sandbox.log • windows-sandbox.log
• rollout.jsonl • rollout.jsonl
• auto-review-rollout.jsonl • auto-review-rollout.jsonl
@@ -7,6 +7,8 @@ expression: popup
The following files will be sent: The following files will be sent:
• codex-logs.log • codex-logs.log
• codex-doctor-report.json • codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• auto-review-rollout-thread-1.jsonl • auto-review-rollout-thread-1.jsonl
• codex-connectivity-diagnostics.txt • codex-connectivity-diagnostics.txt
@@ -1,6 +1,5 @@
--- ---
source: tui/src/chatwidget/tests/popups_and_settings.rs source: tui/src/chatwidget/tests/popups_and_settings.rs
assertion_line: 2562
expression: popup expression: popup
--- ---
Upload logs? Upload logs?
@@ -8,6 +7,8 @@ expression: popup
The following files will be sent: The following files will be sent:
• codex-logs.log • codex-logs.log
• codex-doctor-report.json • codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• windows-sandbox.log • windows-sandbox.log
• auto-review-rollout-thread-1.jsonl • auto-review-rollout-thread-1.jsonl
• codex-connectivity-diagnostics.txt • codex-connectivity-diagnostics.txt