zeroclaw-labs/zeroclaw · error

ModelPinnedProviderBuilder: inner() is required

Error message

ModelPinnedProviderBuilder: inner() is required

What it means

ModelPinnedProviderBuilder.build() requires the delegate provider via .inner(); the pinned wrapper has nothing to route to without it. This expect() panic fires when .inner() was never called. Together with pinned_model() (error 1522) it forms the builder's mandatory contract.

Source

Thrown at crates/zeroclaw-providers/src/model_pin.rs:54

    /// The inner provider whose model this pin overrides. Required.
    pub fn inner(mut self, inner: Box<dyn ModelProvider>) -> Self {
        self.inner = Some(inner);
        self
    }

    /// # Panics
    /// Panics if [`Self::pinned_model`] or [`Self::inner`] was not
    /// called — neither has a sensible default.
    pub fn build(self) -> ModelPinnedProvider {
        ModelPinnedProvider {
            alias: self.alias,
            pinned_model: self
                .pinned_model
                .expect("ModelPinnedProviderBuilder: pinned_model() is required"),
            inner: self
                .inner
                .expect("ModelPinnedProviderBuilder: inner() is required"),
        }
    }
}

impl ModelPinnedProvider {
    /// Entry point. Only `alias` is taken positionally; the required
    /// `pinned_model` and `inner` provider both go through labelled
    /// chain methods so call sites cannot silently swap them.
    pub fn builder(alias: &str) -> ModelPinnedProviderBuilder {
        ModelPinnedProviderBuilder {
            alias: alias.to_string(),
            pinned_model: None,
            inner: None,
        }
    }

    pub(crate) fn pinned_model(&self) -> &str {
        &self.pinned_model

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Call .inner(Box::new(provider)) (any ModelProvider implementation) before .build().
  2. Build the inner provider first, then the pinned wrapper, so inner is always available at construction time.
  3. Cover new pinning setups with a construction test to catch the missing setter in CI.

Example fix

// before
let p = ModelPinnedProvider::builder()
    .alias("fast")
    .pinned_model("gpt-4o-mini")
    .build(); // panics: inner() is required

// after
let p = ModelPinnedProvider::builder()
    .alias("fast")
    .pinned_model("gpt-4o-mini")
    .inner(Box::new(inner_provider))
    .build();
Defensive patterns

Strategy: validation

Validate before calling

// Build the delegate first so inner is provably available:
let inner: Box<dyn ModelProvider> = make_inner_provider(&config)?; // Result-returning
let provider = ModelPinnedProvider::builder()
    .alias(alias)
    .pinned_model(model)
    .inner(inner)
    .build();

Try / catch

let provider = std::panic::catch_unwind(|| builder.build())
    .map_err(|_| anyhow::anyhow!("ModelPinnedProviderBuilder contract violated: inner provider required"))?;

Prevention

When it happens

Trigger: Constructing ModelPinnedProvider with .alias(...) and .pinned_model(...) but no .inner(provider) call before .build().

Common situations: Copy-pasting a pinned-provider setup and forgetting the delegate; refactoring provider wiring so the inner provider value is produced later than build() and the setter call is dropped.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/7d51e9ea23afff7c. Report an issue: GitHub.