astrid-runtime/astrid · error · std::io::Error::NotFound

host did not resolve to any address

Error message

host did not resolve to any address

What it means

SafeDnsResolver::resolve completed the DNS lookup but produced an empty address set, so there is no address to connect to. The resolver reports this as an io::Error with ErrorKind::NotFound (which the caller maps to the typed `dns-error`) and sets `dns_failed` — explicitly not an SSRF block, just an ordinary resolution miss. It is surfaced to guests as 'DNS could not resolve the hostname'.

Source

Thrown at crates/astrid-capsule/src/engine/wasm/host/http/ssrf.rs:98

            let (safe_addrs, saw_unsafe) = filter_safe_addrs(addrs, exempt);

            if safe_addrs.is_empty() {
                // All resolved addresses failed the airlock: a genuine SSRF
                // block. Mark `tripped` so the caller can emit the typed
                // `airlock-rejected` instead of a generic connection error.
                if saw_unsafe {
                    tripped.store(true, Ordering::Relaxed);
                    return Err(Box::new(std::io::Error::new(
                        std::io::ErrorKind::PermissionDenied,
                        "DNS resolved to an unauthorized private or local IP address",
                    ))
                        as Box<dyn std::error::Error + Send + Sync>);
                }
                // Resolved to an empty address set: an ordinary resolution miss,
                // not an airlock block — mark `dns_failed`, not `tripped`.
                dns_failed.store(true, Ordering::Relaxed);
                return Err(Box::new(std::io::Error::new(
                    std::io::ErrorKind::NotFound,
                    "host did not resolve to any address",
                ))
                    as Box<dyn std::error::Error + Send + Sync>);
            }

            let iter: reqwest::dns::Addrs = Box::new(safe_addrs.into_iter());
            Ok(iter)
        })
    }
}

/// Partition resolved addresses into the airlock-safe set, reporting
/// whether any address was dropped as unsafe. An all-unsafe result (empty
/// safe set with `saw_unsafe == true`) is an airlock rejection; an empty
/// input is an ordinary resolution miss.
///
/// `exempt` (the operator allowlist matched this host:port at pre-flight)

View on GitHub (pinned to affd8760f4)

Solutions

  1. Fix the hostname in the guest's request (check spelling and that the DNS record exists via an external resolver)
  2. Verify the target service is still deployed and its DNS record (A/AAAA) is published in the resolver the sandbox uses
  3. Retry after DNS propagation if the record was just created (new domains can take time to propagate)
  4. If the name only exists on the operator's network, use the exempt/allowlisted host path or expose a public endpoint

Example fix

// before: stale hostname in guest config
let base = "https://api-old.internal.example.com"; // record deleted
// after: current published hostname
let base = "https://api.internal.example.com";
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight check that the host resolves to at least one address:
fn host_resolves(host: &str) -> bool {
    tokio::net::lookup_host((host, 0u16))
        .map(|mut addrs| addrs.next().is_some())
        .unwrap_or(false)
}

Try / catch

match result {
    Err(e) if e.kind() == std::io::ErrorKind::NotFound
        || e.to_string().contains("did not resolve") => {
        eprintln!("DNS miss for host; retrying with backoff");
        retry_with_backoff(host, 3);
    }
    other => propagate(other),
}

Prevention

When it happens

Trigger: A guest requests a URL whose hostname resolves to zero addresses: newly registered or deleted DNS records, a host removed from internal DNS, IPv6-only/AAAA-only environments where lookup yields no usable addresses for the requested family, or empty stub-zone answers.

Common situations: Typo'd or decommissioned hostname in guest configuration; DNS TTL expiry after a service was torn down; split-horizon DNS where the sandbox's resolver has no record for an internal name; a service's DNS record removed during a deploy while guest code still calls it.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/806f9cd2f9689023. Report an issue: GitHub.