mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
[codex] Track plugin install and import telemetry failures (#28731)
## Summary - Track plugin install failures through the unified `codex_plugin_install_failed` event for local installs, remote install preflight failures, bundle failures, and remote catalog/backend failures. - Send classified `error_type` values in plugin install failure analytics instead of raw error strings. - Stop sending raw external-agent import errors in analytics while preserving raw failure details in app-facing import notifications/history. - Keep raw plugin/migration diagnostics in `tracing::warn!` logs. - Keep remote failure plugin names as the existing local placeholder (`unknown`) and remove the extra telemetry plugin-name override. - Change `ExternalAgentConfigImportParams.source` from a generated enum to `string | null`, with legacy `claudeCode` / `claudeCowork` inputs normalized to existing analytics values. ## Testing
This commit is contained in:
1 parent
5867b529ae
commit
3959ab0ffc
25 files changed
+1314
-97
No files matched your search
@@ -9,7 +9,11 @@ use crate::events::CodexCommandExecutionEventParams;
|
||||
use crate::events::CodexCommandExecutionEventRequest;
|
||||
use crate::events::CodexCompactionEventRequest;
|
||||
use crate::events::CodexHookRunEventRequest;
|
||||
use crate::events::CodexOnboardingExternalAgentImportFailureEventRequest;
|
||||
use crate::events::CodexOnboardingExternalAgentImportFailureMetadata;
|
||||
use crate::events::CodexPluginEventRequest;
|
||||
use crate::events::CodexPluginInstallFailedEventRequest;
|
||||
use crate::events::CodexPluginInstallFailedMetadata;
|
||||
use crate::events::CodexPluginUsedEventRequest;
|
||||
use crate::events::CodexReviewEventParams;
|
||||
use crate::events::CodexReviewEventRequest;
|
||||
@@ -51,10 +55,13 @@ use crate::facts::CompactionStatus;
|
||||
use crate::facts::CompactionStrategy;
|
||||
use crate::facts::CompactionTrigger;
|
||||
use crate::facts::CustomAnalyticsFact;
|
||||
use crate::facts::ExternalAgentConfigImportCompletedInput;
|
||||
use crate::facts::ExternalAgentConfigImportFailureInput;
|
||||
use crate::facts::HookRunFact;
|
||||
use crate::facts::HookRunInput;
|
||||
use crate::facts::InputError;
|
||||
use crate::facts::InvocationType;
|
||||
use crate::facts::PluginInstallFailedInput;
|
||||
use crate::facts::PluginState;
|
||||
use crate::facts::PluginStateChangedInput;
|
||||
use crate::facts::PluginUsedInput;
|
||||
@@ -3054,6 +3061,36 @@ fn plugin_management_event_serializes_expected_shape() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_install_failed_event_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::PluginInstallFailed(CodexPluginInstallFailedEventRequest {
|
||||
event_type: "codex_plugin_install_failed",
|
||||
event_params: CodexPluginInstallFailedMetadata {
|
||||
plugin: codex_plugin_metadata(sample_plugin_metadata()),
|
||||
error_type: "store_io".to_string(),
|
||||
},
|
||||
});
|
||||
|
||||
let payload = serde_json::to_value(&event).expect("serialize plugin install failed event");
|
||||
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!({
|
||||
"event_type": "codex_plugin_install_failed",
|
||||
"event_params": {
|
||||
"plugin_id": "sample@test",
|
||||
"plugin_name": "sample",
|
||||
"marketplace_name": "test",
|
||||
"has_skills": true,
|
||||
"mcp_server_count": 2,
|
||||
"connector_ids": ["calendar", "drive"],
|
||||
"product_client_id": originator().value,
|
||||
"error_type": "store_io"
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugin_management_event_can_use_remote_plugin_id_override() {
|
||||
let mut plugin = sample_plugin_metadata();
|
||||
@@ -3421,6 +3458,190 @@ async fn reducer_ingests_plugin_state_changed_fact() {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_plugin_install_failed_fact() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::PluginInstallFailed(
|
||||
PluginInstallFailedInput {
|
||||
plugin: sample_plugin_metadata(),
|
||||
error_type: "invalid_plugin".to_string(),
|
||||
},
|
||||
)),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!([{
|
||||
"event_type": "codex_plugin_install_failed",
|
||||
"event_params": {
|
||||
"plugin_id": "sample@test",
|
||||
"plugin_name": "sample",
|
||||
"marketplace_name": "test",
|
||||
"has_skills": true,
|
||||
"mcp_server_count": 2,
|
||||
"connector_ids": ["calendar", "drive"],
|
||||
"product_client_id": originator().value,
|
||||
"error_type": "invalid_plugin"
|
||||
}
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_plugin_install_failed_fact_without_detail() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
let mut plugin = PluginTelemetryMetadata::from_plugin_id(
|
||||
&PluginId::parse("unknown@openai-curated-remote").expect("valid plugin id"),
|
||||
);
|
||||
plugin.remote_plugin_id = Some("plugins~Plugin_00000000000000000000000000000000".to_string());
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::PluginInstallFailed(
|
||||
PluginInstallFailedInput {
|
||||
plugin,
|
||||
error_type: "remote_catalog_unexpected_status".to_string(),
|
||||
},
|
||||
)),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!([{
|
||||
"event_type": "codex_plugin_install_failed",
|
||||
"event_params": {
|
||||
"plugin_id": "plugins~Plugin_00000000000000000000000000000000",
|
||||
"plugin_name": "unknown",
|
||||
"marketplace_name": "openai-curated-remote",
|
||||
"has_skills": null,
|
||||
"mcp_server_count": null,
|
||||
"connector_ids": null,
|
||||
"product_client_id": originator().value,
|
||||
"error_type": "remote_catalog_unexpected_status"
|
||||
}
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_external_agent_config_import_completed_fact() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::ExternalAgentConfigImportCompleted(
|
||||
ExternalAgentConfigImportCompletedInput {
|
||||
import_id: "import-1".to_string(),
|
||||
source: "app_server".to_string(),
|
||||
item_type: "PLUGINS".to_string(),
|
||||
success_count: 2,
|
||||
failed_count: 1,
|
||||
},
|
||||
)),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!([{
|
||||
"event_type": "codex_onboarding_external_agent_import_complete",
|
||||
"event_params": {
|
||||
"import_id": "import-1",
|
||||
"source": "app_server",
|
||||
"type": "PLUGINS",
|
||||
"success_count": 2,
|
||||
"failed_count": 1,
|
||||
"product_client_id": originator().value,
|
||||
}
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_agent_config_import_failure_event_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::ExternalAgentConfigImportFailure(
|
||||
CodexOnboardingExternalAgentImportFailureEventRequest {
|
||||
event_type: "codex_onboarding_external_agent_import_failure",
|
||||
event_params: CodexOnboardingExternalAgentImportFailureMetadata {
|
||||
import_id: "import-1".to_string(),
|
||||
source: "app_server".to_string(),
|
||||
item_type: "SESSIONS".to_string(),
|
||||
failure_stage: "session_missing".to_string(),
|
||||
error_type: "session_missing".to_string(),
|
||||
product_client_id: Some(originator().value),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let payload = serde_json::to_value(&event).expect("serialize import failure event");
|
||||
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!({
|
||||
"event_type": "codex_onboarding_external_agent_import_failure",
|
||||
"event_params": {
|
||||
"import_id": "import-1",
|
||||
"source": "app_server",
|
||||
"type": "SESSIONS",
|
||||
"failure_stage": "session_missing",
|
||||
"error_type": "session_missing",
|
||||
"product_client_id": originator().value,
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reducer_ingests_external_agent_config_import_failure_fact() {
|
||||
let mut reducer = AnalyticsReducer::default();
|
||||
let mut events = Vec::new();
|
||||
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Custom(CustomAnalyticsFact::ExternalAgentConfigImportFailure(
|
||||
ExternalAgentConfigImportFailureInput {
|
||||
import_id: "import-1".to_string(),
|
||||
source: "app_server".to_string(),
|
||||
item_type: "SESSIONS".to_string(),
|
||||
failure_stage: "session_missing".to_string(),
|
||||
error_type: "session_missing".to_string(),
|
||||
},
|
||||
)),
|
||||
&mut events,
|
||||
)
|
||||
.await;
|
||||
|
||||
let payload = serde_json::to_value(&events).expect("serialize events");
|
||||
assert_eq!(
|
||||
payload,
|
||||
json!([{
|
||||
"event_type": "codex_onboarding_external_agent_import_failure",
|
||||
"event_params": {
|
||||
"import_id": "import-1",
|
||||
"source": "app_server",
|
||||
"type": "SESSIONS",
|
||||
"failure_stage": "session_missing",
|
||||
"error_type": "session_missing",
|
||||
"product_client_id": originator().value,
|
||||
}
|
||||
}])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_event_serializes_expected_shape() {
|
||||
let event = TrackEventRequest::TurnEvent(Box::new(CodexTurnEventRequest {
|
||||
|
||||
Reference in new issue
Block a user