zeroclaw-labs/zeroclaw · error · anyhow::Error
Failed to resolve host '{host}'
Error message
Failed to resolve host '{host}' What it means
resolve_host_for_request performs the DNS lookup for the http_request tool via tokio::net::lookup_host and treats two outcomes as failures: the lookup returns an error (surfaced as "Failed to resolve host '{host}': {e}"), or it succeeds but yields zero addresses, which produces this exact message. The empty-success case is a defensive guard: the resolved IP list feeds SSRF validation, and an empty list would silently skip that check, so the request aborts instead. It almost always indicates a broken resolver environment rather than a bad hostname.
Source
Thrown at crates/zeroclaw-tools/src/http_request.rs:768
}
fn extract_port(url: &str) -> anyhow::Result<u16> {
let parsed = reqwest::Url::parse(url)
.map_err(|e| anyhow::Error::msg(format!("Invalid URL format: {e}")))?;
parsed
.port_or_known_default()
.ok_or_else(|| anyhow::Error::msg("URL must include a valid port"))
}
async fn resolve_host_for_request(host: String, port: u16) -> anyhow::Result<Vec<SocketAddr>> {
let addrs = tokio::net::lookup_host((host.as_str(), port))
.await
.map_err(|e| anyhow::Error::msg(format!("Failed to resolve host '{host}': {e}")))?
.collect::<Vec<_>>();
if addrs.is_empty() {
anyhow::bail!("Failed to resolve host '{host}'");
}
Ok(addrs)
}
fn validate_resolved_ips_for_ssrf(
host: &str,
private_resolution_allowed: bool,
ips: &[std::net::IpAddr],
nat64_prefixes: &[domain_guard::Nat64Prefix],
) -> anyhow::Result<()> {
if private_resolution_allowed {
domain_guard::validate_resolved_ips_exclude_metadata(host, ips, nat64_prefixes)
} else {
domain_guard::validate_resolved_ips_are_public(host, ips, nat64_prefixes)
}
}
View on GitHub (pinned to 88bb9c8533)
Solutions
- Run 'getent hosts <host>' and 'dig <host> @<resolver>' in the same environment (same container/pod) to confirm the resolver actually returns records.
- Inspect /etc/nsswitch.conf and /etc/resolv.conf; in minimal images ensure the 'dns' source is present and the nameserver is reachable.
- Retry after network/DNS setup settles — empty answers during resolver startup are transient.
- If relying on search domains, pass the fully qualified name instead of the short name.
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight the same lookup the tool performs:
async fn resolvable(host: &str, port: u16) -> bool {
tokio::net::lookup_host((host, port)).await.map(|it| it.count() > 0).unwrap_or(false)
} Try / catch
for attempt in 0..3 {
match http_request_tool(url.clone()).await {
Ok(res) => break Ok(res),
Err(e) if e.to_string().starts_with("Failed to resolve host") && attempt < 2 => {
tokio::time::sleep(Duration::from_millis(500 * (attempt + 1))).await;
}
Err(e) => break Err(e),
}
} Prevention
- Verify container images ship a working /etc/nsswitch.conf (with the dns source) and a reachable nameserver in /etc/resolv.conf.
- Use fully qualified names instead of relying on search domains.
- Treat empty-resolution as environment breakage: diagnose with 'getent hosts <host>' in the same pod before blaming the URL.
When it happens
Trigger: An http_request call to a host whose getaddrinfo-style lookup completes successfully but returns no addresses: minimal/musl containers with a stripped /etc/nsswitch.conf, resolvers that answer with zero A/AAAA records, transient states while systemd-resolved or a VPN DNS proxy is starting, or search-domain configurations where the queried name matches nothing.
Common situations: Distroless or Alpine-based containers without the dns module in nsswitch.conf, Docker/Kubernetes pods during network setup, hosts with an unreachable or flapping DNS server, and CI runners where /etc/resolv.conf points at a resolver that returns empty answers.
Related errors
- exceeded {} reconnect attempts, giving up
- xAI OAuth discovery failed ({status}): {body}
- no public catalog for family {family:?}
- URL must include a valid host
- Generated image URL host must not end with a dot
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0238ce8b64d27d2b.
Report an issue: GitHub.