odysseus-dev/odysseus · error · SkillImportError
outbound URL blocked: host did not resolve to a usable addre
Error message
outbound URL blocked: host did not resolve to a usable address
What it means
Raised in _resolve_and_check_url (services/memory/skill_importer.py) when check_outbound_url passed but none of the recorded DNS answers could be parsed into a usable ipaddress object (_validated_ips returned nothing). The importer pins every connection to the validated IP snapshot (via _PinnedBackend), so an empty/non-parseable answer set is fatal even though the URL itself looked acceptable.
Source
Thrown at services/memory/skill_importer.py:115
"""Return the exact address snapshot approved for one fetch hop."""
resolved_ips: List[str] = []
def _recording_resolver(host: str) -> List[str]:
answers = list(_default_resolver(host))
resolved_ips[:] = answers
return answers
ok, reason = check_outbound_url(
url,
block_private=True,
resolver=_recording_resolver,
)
if not ok:
raise SkillImportError(f"outbound URL blocked: {reason}")
pinned_ips = _validated_ips(resolved_ips)
if not pinned_ips:
raise SkillImportError("outbound URL blocked: host did not resolve to a usable address")
return pinned_ips
# Backward compatibility alias for tests importing _check_fetch_url directly
_check_fetch_url = _resolve_and_check_url
class _PinnedBackend(httpcore.NetworkBackend):
"""Connect only to addresses from one validated DNS snapshot."""
def __init__(self, ips: List[ipaddress._BaseAddress]):
self._ips = [str(ip) for ip in ips]
self._real = httpcore.SyncBackend()
def connect_tcp(
self,
host: str,
port: int,View on GitHub (pinned to f9235ebbf1)
Solutions
- Check what the host actually resolves to: dig +short <host> / python -c 'import socket; print(socket.getaddrinfo("<host>", 443))'.
- Fix or bypass the broken resolver (point /etc/resolv.conf at a working resolver, disconnect the VPN DNS).
- Retry the import — transient partial DNS answers can produce an empty validated set once and succeed next time.
- If the host genuinely has no A/AAAA record, the URL is dead; find the correct GitHub URL for the skill.
Example fix
# before
$ python -c "import socket; print(socket.getaddrinfo('raw.githubusercontent.com', 443))"
[] # resolver returns nothing parseable
SkillImportError: outbound URL blocked: host did not resolve to a usable address
# after
$ sudo resolvectl flush-caches && dig +short raw.githubusercontent.com # returns real A records
# retry the import Defensive patterns
Strategy: retry
Validate before calling
import socket
def host_resolves_to_ips(host: str) -> list[str]:
try:
return list({info[4][0] for info in socket.getaddrinfo(host, 443, proto=socket.IPPROTO_TCP)})
except socket.gaierror:
return [] Type guard
def host_has_usable_address(host: str) -> bool:
ips = host_resolves_to_ips(host)
return bool(ips) and all(ip.count('.') == 3 or ':' in ip for ip in ips) Try / catch
import time
from services.memory.skill_importer import SkillImportError
for attempt in range(3):
try:
import_skill(url)
break
except SkillImportError as e:
if 'did not resolve' in str(e) and attempt < 2:
time.sleep(2 * (attempt + 1)) # transient DNS; flush caches / check resolver if persistent
continue
raise Prevention
- Preflight DNS with getaddrinfo before starting an import job.
- Use a stable resolver (public DNS) in containers/CI to avoid flaky answer sets.
- If a VPN's DNS plugin breaks resolution, import outside the VPN or fix the plugin.
When it happens
Trigger: A host whose DNS returns only record types the resolver surfaces as non-IP strings (bare CNAME chains, IPv6 addresses that fail ipaddress parsing after the '%' scope-strip, or resolver software returning nonstandard values); a recording resolver that returned an empty list the checker happened to tolerate; DNS64/NAT64 environments returning synthetic addresses in odd formats.
Common situations: Exotic or broken local resolvers (systemd-resolved in odd modes, VPN DNS plugins); hosts that are CNAME-only at the moment of resolution; transient DNS misconfiguration during import; test environments mocking DNS with non-IP strings.
Related errors
- outbound URL blocked: {reason}
- too many redirects while fetching skill bundle
- {context} must stay on GitHub (got {host or 'unknown host'})
- unsafe path: {rel!r}
- unsupported URL scheme: {scheme}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/b632043b0b838175.
Report an issue: GitHub.