{"record":{"id":"51e5d818f339df76","repo":"rustfs/rustfs","slug":"plugin-plugin-id-is-not-an-external-plugin","errorCode":null,"errorMessage":"plugin {plugin_id} is not an external plugin","messagePattern":"plugin (.+?) is not an external plugin","errorType":"exception","errorClass":"TargetPluginExternalActionError","httpStatus":null,"severity":"error","filePath":"crates/targets/src/control_plane.rs","lineNumber":261,"sourceCode":"pub struct TargetPluginExternalFlowGateStatus {\n    pub enabled: bool,\n    pub install_requires_signature: bool,\n    pub install_requires_provenance: bool,\n    pub runtime_allows_external_sidecars: bool,\n    pub runtime_requires_sandbox: bool,\n    pub runtime_requires_provenance: bool,\n    pub circuit_breaker_closed: bool,\n    pub max_queue_depth: usize,\n    pub failure_threshold: usize,\n    pub redacts_error_details: bool,\n}\n\n#[derive(Debug, Error, PartialEq, Eq)]\npub enum TargetPluginExternalActionError {\n    #[error(\"external plugin flow is disabled\")]\n    ExternalFlowDisabled,\n\n    #[error(\"plugin {plugin_id} is not an external plugin\")]\n    NotExternalPlugin { plugin_id: String },\n\n    #[error(\"plugin {plugin_id} is not installed\")]\n    NotInstalled { plugin_id: String },\n\n    #[error(\"plugin {plugin_id} has no previous revision for rollback\")]\n    MissingPreviousRevision { plugin_id: String },\n\n    #[error(\"external plugin install policy denied action: {reason}\")]\n    InstallPolicyDenied { reason: String },\n\n    #[error(\"external plugin runtime policy denied action: {reason}\")]\n    RuntimePolicyDenied { reason: String },\n\n    #[error(\"external plugin circuit breaker is open\")]\n    CircuitBreakerOpen,\n\n    #[error(\"external plugin {plugin_id} has no installable artifact for host target triple {target_triple}\")]","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/rustfs/rustfs/blob/35af688cd9d41b4346fbe27dcf7250ba72046c1f/crates/targets/src/control_plane.rs#L243-L279","documentation":"TargetPluginExternalActionError::NotExternalPlugin is returned by validate_external_action_subject (crates/targets/src/control_plane.rs:424-432), the first check plan_external_target_plugin_action performs. The external planning flow only accepts marketplace manifests whose packaging field equals TargetPluginPackaging::External; builtin-packaged plugins (e.g. builtin:webhook) are managed through the builtin registration path instead.","triggerScenarios":"Passing builtin_target_marketplace_manifest(\"webhook\") or any manifest with packaging != External to plan_external_target_plugin_action, for any action (Install, Enable, Disable, Rollback).","commonSituations":"Admin handlers routing every plugin through the external flow; tests using builtin manifests as convenient fixtures; a manifest author forgetting packaging: external in marketplace metadata.","solutions":["Branch on manifest.packaging: use the builtin registry/installation path (builtin_target_plugin_installation) for builtin plugins and the external flow only for TargetPluginPackaging::External","Use an external-packaged manifest such as example_external_webhook_plugin().manifest when exercising the external flow","Set packaging = TargetPluginPackaging::External in the marketplace manifest if the plugin really ships as a downloadable sidecar artifact"],"exampleFix":"// before\nlet result = plan_external_target_plugin_action(\n    &builtin_target_marketplace_manifest(\"webhook\"), // packaging: Builtin\n    TargetPluginExternalAction::Install, &inst, &gate, triple); // NotExternalPlugin\n\n// after\nif manifest.packaging == TargetPluginPackaging::External {\n    plan_external_target_plugin_action(&manifest, action, &inst, &gate, triple)?;\n} else {\n    let installation = builtin_target_plugin_installation(&manifest); // builtin path\n}","handlingStrategy":"validation","validationCode":"use crate::manifest::TargetPluginPackaging;\n\nif manifest.packaging != TargetPluginPackaging::External {\n    // route to the builtin registration/installation path instead\n    let installation = builtin_target_plugin_installation(&builtin_manifest);\n}","typeGuard":"fn is_external_plugin(manifest: &TargetPluginMarketplaceManifest) -> bool {\n    manifest.packaging == TargetPluginPackaging::External\n}","tryCatchPattern":"match plan_external_target_plugin_action(&manifest, action, &inst, &gate, triple) {\n    Err(TargetPluginExternalActionError::NotExternalPlugin { plugin_id }) => /* use the builtin path for plugin_id */,\n    Ok(decision) => { /* apply */ }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Branch on manifest.packaging before choosing the external or builtin flow","In marketplace metadata, always set packaging explicitly","Remember builtin plugin ids carry the 'builtin:' prefix - a quick smell test"],"tags":["plugin-management","type-mismatch","rustfs","control-plane"],"backgroundTag":"wrong-resource-type","analyzedSha":"35af688cd9d41b4346fbe27dcf7250ba72046c1f","analyzedAt":"2026-08-20T21:57:04.799Z","contentChangedAt":"2026-08-20T21:57:04.799Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}