zeroclaw-labs/zeroclaw · error · anyhow::Error
ACP returned unknown optionId: {option_id}
Error message
ACP returned unknown optionId: {option_id} What it means
OpenAiCompatibleBuilder::build() requires display_name(), base_url(), and auth_style() — the doc comment states all three carry no sensible default and every real call site sets them. This expect fires when the display name (name field) was not set before build(). It is reached from make_model_provider and the streaming/test paths listed under RAISED IN.
Source
Thrown at crates/zeroclaw-channels/src/acp_channel.rs:117
// Response shape: { outcome: { outcome: "selected", optionId: "..." } | { outcome: "cancelled" } }
let outcome = response.get("outcome");
let kind = outcome
.and_then(|o| o.get("outcome"))
.and_then(|s| s.as_str())
.unwrap_or("");
match kind {
"selected" => {
let option_id = outcome
.and_then(|o| o.get("optionId"))
.and_then(|s| s.as_str())
.unwrap_or("");
let idx = option_id
.strip_prefix("choice-")
.and_then(|s| s.parse::<usize>().ok());
match idx.and_then(|i| choices.get(i)) {
Some(text) => Ok(Some(text.clone())),
None => anyhow::bail!("ACP returned unknown optionId: {option_id}"),
}
}
"cancelled" => Ok(None),
other => anyhow::bail!("ACP returned unexpected outcome: {other}"),
}
}
/// Form-mode elicitation path — issues `elicitation/create` with a
/// single-select schema. Used when the client advertises
/// `clientCapabilities.elicitation.form`.
async fn request_choice_via_elicitation(
&self,
question: &str,
choices: &[String],
timeout: Duration,
) -> anyhow::Result<Option<String>> {
let req = ElicitationRequest {
session_id: self.session_id.clone(),View on GitHub (pinned to 88bb9c8533)
Solutions
- Set display_name() (along with base_url() and auth_style()) before build().
- Validate compatible-provider config entries at load time for all three required keys and fail with a message naming the provider.
- Restructure the builder so the three required values are constructor parameters, leaving Option setters for optional behavior.
- Run the builder-related tests (build_native_tool_chat_request_*, capable_endpoint_*) after changes.
Example fix
// before
let provider = OpenAiCompatibleModelProvider::builder()
.base_url(base_url)
.auth_style(auth_style)
.build(); // panics: display_name() is required
// after
let provider = OpenAiCompatibleModelProvider::builder()
.display_name("my-ollama")
.base_url(base_url)
.auth_style(auth_style)
.build(); Defensive patterns
Strategy: validation
Validate before calling
// Validate all three required keys for compatible providers before build():
fn compatible_entry_ok(cfg: &serde_json::Value) -> Result<(), String> {
for key in ["display_name", "base_url", "auth_style"] {
if cfg.get(key).and_then(|v| v.as_str()).map_or(true, |s| s.is_empty()) {
return Err(format!("openai-compatible provider: `{key}` missing"));
}
}
Ok(())
} Type guard
fn compatible_provider_complete(cfg: &serde_json::Value) -> bool {
["display_name", "base_url", "auth_style"].iter().all(|k| {
cfg.get(*k).and_then(|v| v.as_str()).map_or(false, |s| !s.trim().is_empty())
})
} Try / catch
let p = std::panic::catch_unwind(|| {
OpenAiCompatibleModelProvider::builder()
.display_name(name.clone())
.base_url(url.clone())
.auth_style(style)
.build()
});
match p {
Ok(provider) => provider,
Err(_) => { /* report which of the three required setters was skipped */ }
} Prevention
- Require a human-readable display name in provider config templates and examples
- Validate the three required keys at config load with per-key messages
- Model required builder fields as constructor parameters in your own APIs
- Run the builder regression tests after any provider-config refactor
When it happens
Trigger: Calling build() without .display_name(...) — typically a generic OpenAI-compatible provider config entry added without a display label, so the mapping to the builder skips the name setter.
Common situations: A user adds an openai-compatible provider (Ollama, vLLM, OpenRouter-style) and omits the display-name key; a config schema change renames the field so the mapping silently drops it; new code constructs the builder for tests and forgets one of the three required setters.
Related errors
- ACP request_permission failed: {} ({})
- ACP request_permission timed out after {timeout:?}
- OpenAiCompatibleBuilder: base_url() is required
- purge_agent not supported by this memory backend
- rename_agent not supported by this memory backend
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/b1bec247dd347ab7.
Report an issue: GitHub.