xai-org/grok-build · error · ToolError

Failed to build web search tool: {e}

Error message

Failed to build web search tool: {e}

What it means

The web search client constructs an rs::WebSearchToolArgs with allowed_domains filters and calls .build(); when the underlying builder rejects the arguments it maps the error to "Failed to build web search tool: {e}". This happens before any network I/O, so the cause is invalid tool configuration — typically malformed or invalid domain entries in allowed_domains.

Source

Thrown at crates/codegen/xai-grok-tools/src/implementations/web_search/client.rs:155

    /// `excluded_domains` is injected into the tool's `filters` after
    /// serialization (the backend Responses API accepts it). The request always
    /// carries exactly one tool (`web_search`) at index 0.
    fn build_request_json(
        &self,
        query: &str,
        allowed_domains: Option<Vec<String>>,
        excluded_domains: Option<Vec<String>>,
    ) -> Result<serde_json::Value, xai_tool_runtime::ToolError> {
        let err = |msg: String| {
            xai_tool_runtime::ToolError::execution(
                xai_tool_protocol::ToolId::new("web_search").expect("valid"),
                msg,
            )
        };
        let web_search = rs::WebSearchToolArgs::default()
            .filters(rs::WebSearchToolFilters { allowed_domains })
            .build()
            .map_err(|e| err(format!("Failed to build web search tool: {e}")))?;
        let request = rs::CreateResponseArgs::default()
            .model(self.model.clone())
            .input(query.to_string())
            .tools(vec![rs::Tool::WebSearch(web_search)])
            .store(false)
            .temperature(0.1)
            .top_p(0.95)
            .max_output_tokens(8192u32)
            .build()
            .map_err(|e| err(format!("Failed to build request: {e}")))?;
        let mut body = serde_json::to_value(&request)
            .map_err(|e| err(format!("Failed to serialize request: {e}")))?;
        if let Some(excluded) = excluded_domains.filter(|d| !d.is_empty()) {
            let tool = body
                .get_mut("tools")
                .and_then(|t| t.as_array_mut())
                .and_then(|arr| arr.first_mut())
                .and_then(|t| t.as_object_mut());

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Inspect the wrapped {e} message; it names the exact builder validation that failed.
  2. Normalize allowed_domains to bare hostnames ("example.com"), stripping scheme/path/wildcards.
  3. Check the installed rs crate version's WebSearchToolArgs/WebSearchToolFilters docs for changed required fields.
  4. Test with allowed_domains = None/empty to confirm the tool builds without filters, then re-add entries one at a time.

Example fix

// before
filters: WebSearchToolFilters { allowed_domains: Some(vec!["https://docs.example.com"] ) }
// after
filters: WebSearchToolFilters { allowed_domains: Some(vec!["docs.example.com"]) }
Defensive patterns

Strategy: validation

Validate before calling

function normalizeDomains(domains) {
  if (!domains) return undefined;
  const bare = domains.map(d => String(d)
    .replace(/^https?:\/\//, '')
    .replace(/\/.*$/, '')
    .replace(/^\*\./, ''));
  return bare.every(d => /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(d)) ? bare : undefined;
}

Type guard

function isValidDomainList(d: unknown): d is string[] {
  return Array.isArray(d) && d.length > 0 &&
    d.every(x => typeof x === 'string' && /^[a-z0-9.-]+\.[a-z]{2,}$/i.test(x));
}

Try / catch

try {
  result = await client.search(query, allowedDomains);
} catch (e) {
  if (String(e).startsWith('Failed to build web search tool')) {
    result = await client.search(query, undefined); // retry without filters
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling search or search_with_titles with allowed_domains containing entries the builder rejects (invalid hostnames, protocols like "https://" in a domain, empty strings that pass the filter, or builder-required fields missing due to a version change in the rs crate).

Common situations: Passing full URLs instead of bare domains; upgrading the responses/web-search crate where WebSearchToolArgs validation became stricter; config files supplying wildcard or malformed domain patterns.

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/6036e1f738e621a9. Report an issue: GitHub.