Log plugin install failure subtypes (#31518)

## Summary

- classify plugin store and remote bundle I/O failures by privacy-safe
operation context
- include the normalized `sub_error_type` in structured plugin-install
failure warnings
- leave analytics events and schemas unchanged

## Why

Top-level error types such as `store_io` collapse multiple filesystem
operations, making logs harder to diagnose. The subtype identifies the
failed operation without logging paths or new exception details.

## Impact

Plugin-install warning logs gain an optional normalized subtype.
Analytics payloads, app-server APIs, and stored data are unchanged.

## Validation

- `just fmt`
- `just test -p codex-core-plugins -p codex-app-server` (1,235 passed;
14 failed and 6 timed out because local `sandbox-exec` was denied,
`test_stdio_server` was unavailable, or user-level skill fixtures were
loaded)
This commit is contained in:
charlesgong-openai
2026-07-08 11:06:45 -04:00
committed by GitHub
parent f17a57b7d5
commit 1ee0e9a949
4 changed files with 56 additions and 0 deletions

View File

@@ -1548,6 +1548,7 @@ impl PluginRequestProcessor {
&remote_marketplace_name,
/*plugin_id*/ None,
error_type,
/*sub_error_type*/ None,
err.to_string(),
);
remote_plugin_catalog_error_to_jsonrpc(
@@ -1591,11 +1592,13 @@ impl PluginRequestProcessor {
)
.map_err(|err| {
let error_type = remote_plugin_bundle_install_error_type(&err);
let sub_error_type = err.sub_error_type();
self.track_plugin_install_failed_for_remote_plugin(
&remote_plugin_id,
&actual_remote_marketplace_name,
Some(&resolved_plugin_id),
error_type,
sub_error_type,
err.to_string(),
);
remote_plugin_bundle_install_error_to_jsonrpc(err)
@@ -1608,11 +1611,13 @@ impl PluginRequestProcessor {
.await
.map_err(|err| {
let error_type = remote_plugin_bundle_install_error_type(&err);
let sub_error_type = err.sub_error_type();
self.track_plugin_install_failed_for_remote_plugin(
&remote_plugin_id,
&actual_remote_marketplace_name,
Some(&resolved_plugin_id),
error_type,
sub_error_type,
err.to_string(),
);
remote_plugin_bundle_install_error_to_jsonrpc(err)
@@ -1635,6 +1640,7 @@ impl PluginRequestProcessor {
&actual_remote_marketplace_name,
Some(&result.plugin_id),
error_type,
/*sub_error_type*/ None,
err.to_string(),
);
remote_plugin_catalog_error_to_jsonrpc(err, "install remote plugin")
@@ -1732,12 +1738,14 @@ impl PluginRequestProcessor {
marketplace_name: &str,
plugin_id: Option<&PluginId>,
error_type: &'static str,
sub_error_type: Option<String>,
error_message: String,
) {
tracing::warn!(
remote_plugin_id = %remote_plugin_id,
marketplace_name = %marketplace_name,
error_type = %error_type,
sub_error_type = sub_error_type.as_deref(),
error = %error_message,
"remote plugin install failed"
);

View File

@@ -1283,6 +1283,7 @@ impl PluginsManager {
self.track_plugin_install_failed(
&plugin_id,
plugin_install_error_type(&err),
plugin_install_sub_error_type(&err),
err.to_string(),
);
Err(err)
@@ -1345,6 +1346,7 @@ impl PluginsManager {
self.track_plugin_install_failed(
&resolved.plugin_id,
plugin_install_error_type(&err),
plugin_install_sub_error_type(&err),
err.to_string(),
);
return Err(err);
@@ -1356,6 +1358,7 @@ impl PluginsManager {
self.track_plugin_install_failed(
&plugin_id,
plugin_install_error_type(&err),
plugin_install_sub_error_type(&err),
err.to_string(),
);
Err(err)
@@ -1383,6 +1386,7 @@ impl PluginsManager {
self.track_plugin_install_failed(
&plugin_id,
marketplace_error_type(err),
/*sub_error_type*/ None,
err.to_string(),
);
} else {
@@ -1398,11 +1402,13 @@ impl PluginsManager {
&self,
plugin_id: &PluginId,
error_type: &'static str,
sub_error_type: Option<String>,
error_message: String,
) {
tracing::warn!(
plugin_id = %plugin_id.as_key(),
error_type = %error_type,
sub_error_type = sub_error_type.as_deref(),
error = %error_message,
"plugin install failed"
);
@@ -2674,6 +2680,16 @@ fn plugin_install_error_type(err: &PluginInstallError) -> &'static str {
}
}
fn plugin_install_sub_error_type(err: &PluginInstallError) -> Option<String> {
match err {
PluginInstallError::Store(err) => err.sub_error_type(),
PluginInstallError::Marketplace(_)
| PluginInstallError::Remote(_)
| PluginInstallError::Config(_)
| PluginInstallError::Join(_) => None,
}
}
fn marketplace_error_type(err: &MarketplaceError) -> &'static str {
match err {
MarketplaceError::Io { .. } => "marketplace_io",

View File

@@ -4,6 +4,7 @@ use crate::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
use crate::store::PluginInstallResult;
use crate::store::PluginStore;
use crate::store::PluginStoreError;
use crate::store::error_context_sub_error_type;
use crate::store::validate_plugin_version_segment;
use codex_login::default_client::build_reqwest_client;
use codex_plugin::PluginId;
@@ -132,6 +133,26 @@ impl RemotePluginBundleInstallError {
fn io(context: &'static str, source: io::Error) -> Self {
Self::Io { context, source }
}
pub fn sub_error_type(&self) -> Option<String> {
match self {
Self::Io { context, .. } => Some(error_context_sub_error_type(context)),
Self::Store(err) => err.sub_error_type(),
Self::MissingReleaseVersion { .. }
| Self::InvalidReleaseVersion { .. }
| Self::MissingBundleDownloadUrl { .. }
| Self::InvalidBundleDownloadUrl { .. }
| Self::UnsupportedBundleDownloadUrlScheme { .. }
| Self::InvalidPluginId { .. }
| Self::DownloadRequest { .. }
| Self::DownloadStatus { .. }
| Self::DownloadBody { .. }
| Self::DownloadTooLarge { .. }
| Self::UnsupportedBundleDownloadFinalUrl { .. }
| Self::ExtractedBundleTooLarge { .. }
| Self::InvalidBundle(_) => None,
}
}
}
pub fn validate_remote_plugin_bundle(

View File

@@ -365,6 +365,17 @@ impl PluginStoreError {
fn io(context: &'static str, source: io::Error) -> Self {
Self::Io { context, source }
}
pub(crate) fn sub_error_type(&self) -> Option<String> {
match self {
Self::Io { context, .. } => Some(error_context_sub_error_type(context)),
Self::Invalid(_) => None,
}
}
}
pub(crate) fn error_context_sub_error_type(context: &str) -> String {
context.to_ascii_lowercase().replace(' ', "_")
}
pub fn plugin_version_for_source(source_path: &Path) -> Result<String, PluginStoreError> {