mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Report I/O subtypes for session config import failures (#37723)
## What changed - Append a stable `std::io::ErrorKind` category to the `failed_to_load_session_config` subtype, including categories such as `invalid_data`, `not_found`, and `permission_denied`. - Propagate the categorized subtype through session import failure reporting. ## Testing - Add an app-server test that verifies an invalid config reports `failed_to_load_session_config_invalid_data` in both the import completion notification and analytics event. GitOrigin-RevId: e9a1a7cd36979911f422805180dd91b6ed65e14e
This commit is contained in:
committed by
copyberry
parent
a16863f870
commit
50ef7395fa
@@ -1,5 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -170,7 +171,7 @@ impl ExternalAgentSessionImporter {
|
||||
record_import_error(
|
||||
&mut item_result,
|
||||
stage,
|
||||
Some(sub_error_type),
|
||||
Some(sub_error_type.as_str()),
|
||||
message,
|
||||
Some(source_path.display().to_string()),
|
||||
);
|
||||
@@ -408,8 +409,25 @@ impl ExternalAgentSessionImporter {
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
let io_kind = match err.kind() {
|
||||
ErrorKind::NotFound => "not_found",
|
||||
ErrorKind::PermissionDenied => "permission_denied",
|
||||
ErrorKind::AlreadyExists => "already_exists",
|
||||
ErrorKind::InvalidInput => "invalid_input",
|
||||
ErrorKind::InvalidData => "invalid_data",
|
||||
ErrorKind::IsADirectory => "is_a_directory",
|
||||
ErrorKind::NotADirectory => "not_a_directory",
|
||||
ErrorKind::TimedOut => "timed_out",
|
||||
ErrorKind::WriteZero => "write_zero",
|
||||
ErrorKind::UnexpectedEof => "unexpected_eof",
|
||||
ErrorKind::StorageFull => "storage_full",
|
||||
ErrorKind::QuotaExceeded => "quota_exceeded",
|
||||
ErrorKind::FileTooLarge => "file_too_large",
|
||||
ErrorKind::ReadOnlyFilesystem => "read_only_filesystem",
|
||||
_ => "other",
|
||||
};
|
||||
SessionImportStepFailure::new(
|
||||
"failed_to_load_session_config",
|
||||
format!("failed_to_load_session_config_{io_kind}"),
|
||||
format!("failed to load imported session config: {err}"),
|
||||
)
|
||||
})?;
|
||||
@@ -583,18 +601,18 @@ struct SessionImportFailure {
|
||||
source_path: PathBuf,
|
||||
message: String,
|
||||
stage: &'static str,
|
||||
sub_error_type: &'static str,
|
||||
sub_error_type: String,
|
||||
}
|
||||
|
||||
struct SessionImportStepFailure {
|
||||
sub_error_type: &'static str,
|
||||
sub_error_type: String,
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl SessionImportStepFailure {
|
||||
fn new(sub_error_type: &'static str, message: String) -> Self {
|
||||
fn new(sub_error_type: impl Into<String>, message: String) -> Self {
|
||||
Self {
|
||||
sub_error_type,
|
||||
sub_error_type: sub_error_type.into(),
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1379,6 +1379,111 @@ async fn external_agent_config_import_completed_tracks_analytics_event() -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_agent_config_import_reports_session_config_error_subtype() -> Result<()> {
|
||||
let analytics_server = start_analytics_events_server().await?;
|
||||
let codex_home = TempDir::new()?;
|
||||
write_analytics_config(codex_home.path(), &analytics_server.uri())?;
|
||||
write_chatgpt_auth(
|
||||
codex_home.path(),
|
||||
ChatGptAuthFixture::new("chatgpt-token")
|
||||
.account_id("account-123")
|
||||
.chatgpt_user_id("user-123")
|
||||
.chatgpt_account_id("account-123"),
|
||||
AuthCredentialsStoreMode::File,
|
||||
)?;
|
||||
|
||||
let project_root = codex_home.path().join("repo");
|
||||
let session_dir = external_agent_home(codex_home.path()).join("projects/repo");
|
||||
let session_path = session_dir.join("session.jsonl");
|
||||
let recent_timestamp = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||
std::fs::create_dir_all(&project_root)?;
|
||||
std::fs::create_dir_all(&session_dir)?;
|
||||
std::fs::write(
|
||||
&session_path,
|
||||
serde_json::json!({
|
||||
"type": "user",
|
||||
"cwd": &project_root,
|
||||
"timestamp": &recent_timestamp,
|
||||
"message": { "content": "first request" },
|
||||
})
|
||||
.to_string(),
|
||||
)?;
|
||||
|
||||
let home_dir = codex_home.path().display().to_string();
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.without_auto_env()
|
||||
.with_env_overrides(&[("HOME", Some(home_dir.as_str()))])
|
||||
.build_initialized_with_timeout(DEFAULT_TIMEOUT)
|
||||
.await?;
|
||||
std::fs::write(
|
||||
codex_home.path().join("config.toml"),
|
||||
"chatgpt_base_url = [",
|
||||
)?;
|
||||
|
||||
let request_id = mcp
|
||||
.send_raw_request(
|
||||
"externalAgentConfig/import",
|
||||
Some(serde_json::json!({
|
||||
"source": "test_import",
|
||||
"providerId": "test-provider-42",
|
||||
"migrationItems": [{
|
||||
"itemType": "SESSIONS",
|
||||
"description": "Migrate recent sessions",
|
||||
"cwd": null,
|
||||
"details": {
|
||||
"sessions": [{
|
||||
"path": session_path,
|
||||
"cwd": project_root,
|
||||
"title": "first request"
|
||||
}]
|
||||
}
|
||||
}]
|
||||
})),
|
||||
)
|
||||
.await?;
|
||||
let response: ExternalAgentConfigImportResponse =
|
||||
timeout(DEFAULT_TIMEOUT, mcp.read_response(request_id)).await??;
|
||||
let import_id = assert_import_response(response);
|
||||
let completed: ExternalAgentConfigImportCompletedNotification = timeout(
|
||||
DEFAULT_TIMEOUT,
|
||||
mcp.read_notification("externalAgentConfig/import/completed"),
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(completed.import_id, import_id);
|
||||
assert_eq!(completed.item_type_results.len(), 1);
|
||||
assert_eq!(completed.item_type_results[0].successes.len(), 0);
|
||||
assert_eq!(completed.item_type_results[0].failures.len(), 1);
|
||||
let failure = &completed.item_type_results[0].failures[0];
|
||||
assert_eq!(failure.failure_stage, "session_persist");
|
||||
assert_eq!(
|
||||
failure.sub_error_type.as_deref(),
|
||||
Some("failed_to_load_session_config_invalid_data")
|
||||
);
|
||||
|
||||
let event = wait_for_analytics_event(
|
||||
&analytics_server,
|
||||
DEFAULT_TIMEOUT,
|
||||
"codex_onboarding_external_agent_import_failure",
|
||||
)
|
||||
.await?;
|
||||
let event_params = &event["event_params"];
|
||||
assert_eq!(event_params["import_id"], serde_json::json!(import_id));
|
||||
assert_eq!(event_params["source"], "test_import");
|
||||
assert_eq!(event_params["provider_id"], "test-provider-42");
|
||||
assert_eq!(event_params["type"], "SESSIONS");
|
||||
assert_eq!(event_params["failure_stage"], "session_persist");
|
||||
assert_eq!(
|
||||
event_params["sub_error_type"],
|
||||
"failed_to_load_session_config_invalid_data"
|
||||
);
|
||||
assert!(event_params.get("raw_errors").is_none());
|
||||
assert!(event_params.get("message").is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_agent_config_import_reinstalls_plugins_from_known_marketplaces() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
Reference in New Issue
Block a user