[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:
charlesgong-openai
2026-06-17 13:16:34 -07:00
committed by GitHub
Unverified
parent 5867b529ae
commit 3959ab0ffc
25 changed files with 1314 additions and 97 deletions
+154 -8
View File
@@ -69,6 +69,7 @@ use codex_plugin::AppConnectorId;
use codex_plugin::PluginCapabilitySummary;
use codex_plugin::PluginId;
use codex_plugin::PluginIdError;
use codex_plugin::PluginTelemetryMetadata;
use codex_plugin::app_connector_ids_from_declarations;
use codex_plugin::prompt_safe_plugin_description;
use codex_protocol::protocol::HookEventName;
@@ -1079,12 +1080,29 @@ impl PluginsManager {
&self,
request: PluginInstallRequest,
) -> Result<PluginInstallOutcome, PluginInstallError> {
let resolved = find_installable_marketplace_plugin(
let resolved = match find_installable_marketplace_plugin(
&request.marketplace_path,
&request.plugin_name,
self.restriction_product,
)?;
self.install_resolved_plugin(resolved).await
) {
Ok(resolved) => resolved,
Err(err) => {
self.track_plugin_install_resolution_failed(&err);
return Err(err.into());
}
};
let plugin_id = resolved.plugin_id.clone();
match self.install_resolved_plugin(resolved).await {
Ok(outcome) => Ok(outcome),
Err(err) => {
self.track_plugin_install_failed(
&plugin_id,
plugin_install_error_type(&err),
err.to_string(),
);
Err(err)
}
}
}
pub async fn install_plugin_with_remote_sync(
@@ -1093,21 +1111,101 @@ impl PluginsManager {
auth: Option<&CodexAuth>,
request: PluginInstallRequest,
) -> Result<PluginInstallOutcome, PluginInstallError> {
let resolved = find_installable_marketplace_plugin(
let resolved = match find_installable_marketplace_plugin(
&request.marketplace_path,
&request.plugin_name,
self.restriction_product,
)?;
) {
Ok(resolved) => resolved,
Err(err) => {
self.track_plugin_install_resolution_failed(&err);
return Err(err.into());
}
};
let plugin_id = resolved.plugin_id.as_key();
// This only forwards the backend mutation before the local install flow.
crate::remote_legacy::enable_remote_plugin(
if let Err(err) = crate::remote_legacy::enable_remote_plugin(
&remote_plugin_service_config(config),
auth,
&plugin_id,
)
.await
.map_err(PluginInstallError::from)?;
self.install_resolved_plugin(resolved).await
{
let err = PluginInstallError::from(err);
self.track_plugin_install_failed(
&resolved.plugin_id,
plugin_install_error_type(&err),
err.to_string(),
);
return Err(err);
}
let plugin_id = resolved.plugin_id.clone();
match self.install_resolved_plugin(resolved).await {
Ok(outcome) => Ok(outcome),
Err(err) => {
self.track_plugin_install_failed(
&plugin_id,
plugin_install_error_type(&err),
err.to_string(),
);
Err(err)
}
}
}
fn track_plugin_install_resolution_failed(&self, err: &MarketplaceError) {
let plugin_id = match err {
MarketplaceError::PluginNotFound {
plugin_name,
marketplace_name,
}
| MarketplaceError::PluginNotAvailable {
plugin_name,
marketplace_name,
} => PluginId::new(plugin_name.clone(), marketplace_name.clone()).ok(),
MarketplaceError::Io { .. }
| MarketplaceError::MarketplaceNotFound { .. }
| MarketplaceError::InvalidMarketplaceFile { .. }
| MarketplaceError::PluginsDisabled
| MarketplaceError::InvalidPlugin(_) => None,
};
if let Some(plugin_id) = plugin_id {
self.track_plugin_install_failed(
&plugin_id,
marketplace_error_type(err),
err.to_string(),
);
} else {
tracing::warn!(
error_type = %marketplace_error_type(err),
error = %err,
"plugin install failed while resolving marketplace plugin"
);
}
}
fn track_plugin_install_failed(
&self,
plugin_id: &PluginId,
error_type: &'static str,
error_message: String,
) {
tracing::warn!(
plugin_id = %plugin_id.as_key(),
error_type = %error_type,
error = %error_message,
"plugin install failed"
);
let analytics_events_client = match self.analytics_events_client.read() {
Ok(client) => client.clone(),
Err(err) => err.into_inner().clone(),
};
if let Some(analytics_events_client) = analytics_events_client {
analytics_events_client.track_plugin_install_failed(
PluginTelemetryMetadata::from_plugin_id(plugin_id),
error_type.to_string(),
);
}
}
async fn install_resolved_plugin(
@@ -2173,6 +2271,54 @@ impl PluginInstallError {
}
}
fn plugin_install_error_type(err: &PluginInstallError) -> &'static str {
match err {
PluginInstallError::Marketplace(err) => marketplace_error_type(err),
PluginInstallError::Remote(err) => remote_plugin_mutation_error_type(err),
PluginInstallError::Store(err) => plugin_store_error_type(err),
PluginInstallError::Config(_) => "config",
PluginInstallError::Join(_) => "join",
}
}
fn marketplace_error_type(err: &MarketplaceError) -> &'static str {
match err {
MarketplaceError::Io { .. } => "marketplace_io",
MarketplaceError::MarketplaceNotFound { .. } => "marketplace_not_found",
MarketplaceError::InvalidMarketplaceFile { .. } => "invalid_marketplace_file",
MarketplaceError::PluginNotFound { .. } => "plugin_not_found",
MarketplaceError::PluginNotAvailable { .. } => "plugin_not_available",
MarketplaceError::PluginsDisabled => "plugins_disabled",
MarketplaceError::InvalidPlugin(_) => "invalid_plugin",
}
}
fn remote_plugin_mutation_error_type(err: &RemotePluginMutationError) -> &'static str {
match err {
RemotePluginMutationError::AuthRequired => "remote_mutation_auth_required",
RemotePluginMutationError::UnsupportedAuthMode => "remote_mutation_unsupported_auth_mode",
RemotePluginMutationError::AuthToken(_) => "remote_mutation_auth_token",
RemotePluginMutationError::InvalidBaseUrl(_) => "remote_mutation_invalid_base_url",
RemotePluginMutationError::InvalidBaseUrlPath => "remote_mutation_invalid_base_url_path",
RemotePluginMutationError::Request { .. } => "remote_mutation_request",
RemotePluginMutationError::UnexpectedStatus { .. } => "remote_mutation_unexpected_status",
RemotePluginMutationError::Decode { .. } => "remote_mutation_decode",
RemotePluginMutationError::UnexpectedPluginId { .. } => {
"remote_mutation_unexpected_plugin_id"
}
RemotePluginMutationError::UnexpectedEnabledState { .. } => {
"remote_mutation_unexpected_enabled_state"
}
}
}
fn plugin_store_error_type(err: &PluginStoreError) -> &'static str {
match err {
PluginStoreError::Io { .. } => "store_io",
PluginStoreError::Invalid(_) => "store_invalid",
}
}
#[derive(Debug, thiserror::Error)]
pub enum PluginUninstallError {
#[error("{0}")]