rustfs/rustfs · error · S3HookRegistryError

s3 hook extension {extension_id} is missing capability {capa

Error message

s3 hook extension {extension_id} is missing capability {capability}

What it means

Returned by S3HookRegistry::register_schema (crates/targets/src/runtime/s3_hooks.rs) when the schema kind is S3Hook but its capabilities list does not contain S3_POST_AUTH_HOOK_CAPABILITY ("s3.hook.post_auth.v1"). Only schemas declaring this exact capability may register post-auth hook points.

Source

Thrown at crates/targets/src/runtime/s3_hooks.rs:31

// limitations under the License.

use std::collections::BTreeMap;

use rustfs_extension_schema::{
    ExtensionContractError, ExtensionKind, ExtensionSchema, S3_POST_AUTH_HOOK_CAPABILITY, S3HookContract, S3HookPoint,
    validate_s3_hook_contract,
};
use thiserror::Error;

#[derive(Debug, Error, PartialEq, Eq)]
pub enum S3HookRegistryError {
    #[error(transparent)]
    InvalidContract(#[from] ExtensionContractError),

    #[error("extension {extension_id} is {kind:?}, not an S3 hook")]
    UnsupportedExtensionKind { extension_id: String, kind: ExtensionKind },

    #[error("s3 hook extension {extension_id} is missing capability {capability}")]
    MissingCapability { extension_id: String, capability: &'static str },
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct S3HookRegistration {
    pub extension_id: String,
    pub hook_point: S3HookPoint,
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct S3HookRegistry {
    registrations: BTreeMap<S3HookPoint, Vec<S3HookRegistration>>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct S3HookContext<'a> {
    pub authenticated_principal: &'a str,
    pub bucket: &'a str,

View on GitHub (pinned to 35af688cd9)

Solutions

  1. Add the capability string s3.hook.post_auth.v1 to the schema's capabilities list
  2. Build it from the S3_POST_AUTH_HOOK_CAPABILITY constant instead of a literal
  3. Verify the manifest against the current contract version in rustfs-extension-schema

Example fix

// before
capabilities: vec![hook_cap.into()], // hook_cap == s3.hook.post_auth

// after
use rustfs_extension_schema::S3_POST_AUTH_HOOK_CAPABILITY;
capabilities: vec![S3_POST_AUTH_HOOK_CAPABILITY.to_string()],
Defensive patterns

Strategy: validation

Validate before calling

use rustfs_extension_schema::{S3_POST_AUTH_HOOK_CAPABILITY, ExtensionSchema};

fn declares_post_auth_hook(schema: &ExtensionSchema) -> bool {
    schema.capabilities.iter().any(|c| c.as_str() == S3_POST_AUTH_HOOK_CAPABILITY)
}

if !declares_post_auth_hook(&schema) {
    return Err(format!("schema {} lacks the S3 post-auth hook capability", schema.extension_id));
}

Type guard

fn declares_post_auth_hook(schema: &ExtensionSchema) -> bool {
    schema.capabilities.iter().any(|c| c.as_str() == S3_POST_AUTH_HOOK_CAPABILITY)
}

Try / catch

match s3_hooks.register_schema(&schema, &contract) {
    Err(S3HookRegistryError::MissingCapability { extension_id, capability }) => {
        tracing::warn!(event = "extension.register", result = "missing_capability", extension_id = %extension_id, capability, "manifest must declare {capability}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: register_schema(&schema, &contract) with kind == ExtensionKind::S3Hook but capabilities missing the exact string s3.hook.post_auth.v1 - typically a hand-written capability string with a typo or wrong version suffix.

Common situations: External hook plugin manifests authored by hand; capability renamed or version-bumped in rustfs-extension-schema while the manifest still uses the old string; capabilities list left empty when converting a builtin example into an external plugin.

Related errors


AI-assisted analysis of rustfs/rustfs@35af688cd9 (2026-08-20). Data as JSON: /api/errors/5d008455d611f416. Report an issue: GitHub.