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
2026-07-13 22:50:28 +00:00
committed by copyberry
parent bb947e8e36
commit fb350d1e7d
10 changed files with 197 additions and 13 deletions
@@ -1,4 +1,9 @@
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")]
use codex_feedback::WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME;
@@ -70,17 +75,16 @@ impl FeedbackRequestProcessor {
None => None,
};
if let Some(chatgpt_user_id) = self
.auth_manager
.auth_cached()
.and_then(|auth| auth.get_chatgpt_user_id())
let auth = self.auth_manager.auth_cached();
if let Some(chatgpt_user_id) = auth
.as_ref()
.and_then(codex_login::CodexAuth::get_chatgpt_user_id)
{
tracing::info!(target: "feedback_tags", chatgpt_user_id);
}
if let Some(account_id) = self
.auth_manager
.auth_cached()
.and_then(|auth| auth.get_account_id())
if let Some(account_id) = auth
.as_ref()
.and_then(codex_login::CodexAuth::get_account_id)
{
tracing::info!(target: "feedback_tags", account_id);
}
@@ -197,6 +201,15 @@ impl FeedbackRequestProcessor {
{
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 {
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 {
format!("auto-review-rollout-{thread_id}.jsonl")
}
@@ -292,11 +346,106 @@ fn windows_sandbox_log_attachment(_codex_home: &Path) -> Option<FeedbackAttachme
None
}
#[cfg(all(test, target_os = "windows"))]
#[cfg(test)]
mod tests {
use super::*;
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]
fn windows_sandbox_log_attachment_uses_current_log() {
let codex_home = tempfile::tempdir().expect("create tempdir");
@@ -6,6 +6,7 @@
//! owned by the connector metadata store, not by this module.
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
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.
///
/// 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_hash = sha1_hex(&cache_key_json);
self.codex_home
+1
View File
@@ -36,6 +36,7 @@ pub use connector_runtime::ConnectorRuntimeFetchTicket;
pub use connector_runtime::ConnectorRuntimeManager;
pub use connector_runtime::ConnectorRuntimePayload;
pub use connector_runtime::ConnectorRuntimeSnapshot;
pub use connector_runtime::connector_runtime_cache_path;
pub use connector_runtime::connector_runtime_context_key;
pub use directory_cache::ConnectorDirectoryCacheContext;
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.
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.
pub const WINDOWS_SANDBOX_LOG_ATTACHMENT_FILENAME: &str = "windows-sandbox.log";
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::FEEDBACK_DIAGNOSTICS_ATTACHMENT_FILENAME;
use codex_feedback::FeedbackDiagnostics;
@@ -510,6 +512,16 @@ pub(crate) fn feedback_upload_consent_params(
DOCTOR_REPORT_ATTACHMENT_FILENAME.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 {
header_lines.push(
@@ -1,6 +1,6 @@
---
source: tui/src/bottom_pane/feedback_view.rs
assertion_line: 716
assertion_line: 728
expression: rendered
---
Upload logs?
@@ -8,5 +8,7 @@ Upload logs?
The following files will be sent:
• codex-logs.log
• codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• rollout.jsonl
• auto-review-rollout.jsonl
@@ -1,6 +1,6 @@
---
source: tui/src/bottom_pane/feedback_view.rs
assertion_line: 734
assertion_line: 746
expression: rendered
---
Upload logs?
@@ -8,6 +8,8 @@ Upload logs?
The following files will be sent:
• codex-logs.log
• codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• windows-sandbox.log
• rollout.jsonl
• auto-review-rollout.jsonl
@@ -7,6 +7,8 @@ expression: popup
The following files will be sent:
• codex-logs.log
• codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• auto-review-rollout-thread-1.jsonl
• codex-connectivity-diagnostics.txt
@@ -1,6 +1,5 @@
---
source: tui/src/chatwidget/tests/popups_and_settings.rs
assertion_line: 2562
expression: popup
---
Upload logs?
@@ -8,6 +7,8 @@ expression: popup
The following files will be sent:
• codex-logs.log
• codex-doctor-report.json
• codex-apps-tools-cache.json (if available)
• codex-app-directory-cache.json (if available)
• windows-sandbox.log
• auto-review-rollout-thread-1.jsonl
• codex-connectivity-diagnostics.txt