zeroclaw-labs/zeroclaw · error

ModelPinnedProviderBuilder: pinned_model() is required

Error message

ModelPinnedProviderBuilder: pinned_model() is required

What it means

ModelPinnedProviderBuilder builds a provider that always routes to one fixed model under an alias. The pinned model has no sensible default, so build() panics if .pinned_model() was never called. The doc comment on the builder states that neither required field has a default.

Source

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

        self.pinned_model = Some(model.to_string());
        self
    }

    /// 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,
        }
    }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Call .pinned_model("<provider-model-id>") on the builder before .build().
  2. If the model id comes from configuration, fail fast with a clear config error when it is missing instead of letting build() panic.
  3. Add a test that constructs every pinned provider declared in config.

Example fix

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

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

Strategy: validation

Validate before calling

let pinned = config.pinned_model.clone()
    .ok_or_else(|| anyhow::anyhow!("agent '{}' declares model pinning without a model id", alias))?;
let provider = ModelPinnedProvider::builder()
    .alias(alias)
    .pinned_model(pinned)
    .inner(Box::new(inner))
    .build();

Try / catch

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

Prevention

When it happens

Trigger: Calling ModelPinnedProvider::builder().alias("name").inner(p).build() (or any construction path) without .pinned_model("model-id") beforehand.

Common situations: Setting up model pinning for an agent alias and wiring the delegate provider but forgetting the target model; changing the pinned model to come from config and the config lookup returning nothing so the setter is skipped.

Related errors


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