santifer/career-ops · error · Error

plugin egress: ${hostname} resolved to no addresses

Error message

plugin egress: ${hostname} resolved to no addresses

What it means

Thrown by `resolveAndValidate` (plugins/_net.mjs:97) when `dnsLookup` resolves successfully but returns an empty address array. This is a defensive guard: a well-formed resolver response with zero A/AAAA records is treated as a failure rather than silently producing an empty validated list (which would then connect nowhere or to a default). It is distinct from a DNS error (105) and from a blocked address (107).

Source

Thrown at plugins/_net.mjs:97

    if (isBlockedIp(hostname)) {
      if (allowsLocalhost && isLoopbackLiteral(hostname)) return [hostname];
      throw new Error(`plugin egress to ${hostname} is blocked (private/loopback/metadata range)`);
    }
    return [hostname];
  }

  if (allowsLocalhost && LOOPBACK_HOSTS.has(hostname.toLowerCase())) {
    // Local-AI providers (Ollama/LM Studio). Resolve but allow loopback through.
    return ['127.0.0.1'];
  }

  let addrs;
  try {
    addrs = await dnsLookup(hostname, { all: true });
  } catch (err) {
    throw new Error(`plugin egress: cannot resolve ${hostname} — ${err.message}`);
  }
  if (!addrs.length) throw new Error(`plugin egress: ${hostname} resolved to no addresses`);
  for (const { address } of addrs) {
    if (isBlockedIp(address)) {
      if (allowsLocalhost && isLoopbackLiteral(address)) continue;
      throw new Error(`plugin egress: ${hostname} resolves to a blocked address (${address}) — possible SSRF/rebinding`);
    }
  }
  return addrs.map(a => a.address);
}

function isLoopbackLiteral(ip) {
  if (ip === '::1') return true;
  if (isIP(ip) === 4) return ip.split('.')[0] === '127';
  return false;
}

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Confirm the hostname actually has A/AAAA records: `dig <hostname> A` and `dig <hostname> AAAA`.
  2. If the host is record-less by mistake, add an A record at the DNS provider, or use a hostname that has address records.
  3. If this is a fluke resolver response, retry; if it persists, switch resolver (`/etc/resolv.conf`) or check split-horizon config.
Defensive patterns

Strategy: validation

Validate before calling

import { lookup } from 'node:dns/promises';
import { Resolver } from 'node:dns';
// Confirm a hostname has A/AAAA records before relying on it.
async function assertHasAddressRecords(hostname) {
  const addrs = await new Resolver().resolve4(hostname).catch(() => [])
    .concat(await new Resolver().resolve6(hostname).catch(() => []));
  if (!addrs.length) throw new Error(`'${hostname}' has no A/AAAA records — cannot be a fetch host.`);
}
await assertHasAddressRecords(hostname);

Try / catch

try {
  await resolveAndValidate(hostname);
} catch (err) {
  if (/resolved to no addresses/.test(err.message)) {
    console.error(`Config error: ${err.message} — use a host with address records.`);
  } else throw err;
}

Prevention

When it happens

Trigger: The hostname exists in DNS but has no address records (e.g. only MX/TXT records, an apex with no A/AAAA), or a custom resolver returns `{ all: true }` with an empty list. The check `if (!addrs.length)` fires immediately after a successful lookup.

Common situations: A domain configured only for email (MX records, no A record) mistakenly used as a fetch host; a misbehaving split-horizon DNS returning empty internally; an IPv6-only hostname queried on an IPv4-only resolver; a temporary resolver glitch returning a valid-but-empty response.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/60e1c1c98669199a. Report an issue: GitHub.