different-ai/openwork · error · Error

Attachment hostname ${hostname} did not resolve.

Error message

Attachment hostname ${hostname} did not resolve.

What it means

validateGmailAttachmentAddresses re-checks the addresses actually used for the socket connection (handed to net.connect to close the DNS-rebinding window). A plain Error (not ApiError) is thrown when the resolver returned an empty list — the hostname did not resolve to any address at connect time. It is plain Error because it is evaluated inside the socket connector against pre-resolved answers.

Source

Thrown at apps/server/src/extensions/google-workspace.ts:841

// it the way the managed MCP undici dispatcher does.
async function assertGmailAttachmentHostPublic(url: URL): Promise<void> {
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
  if (isIP(hostname) !== 0) return;
  let addresses: LookupAddress[];
  try {
    addresses = await lookup(hostname, { all: true, verbatim: true });
  } catch {
    throw new ApiError(502, "attachment_fetch_failed", "Attachment url hostname could not be resolved", { hostname });
  }
  if (!addresses.length || addresses.some((entry) => isLocalManagedMcpPrivateAddress(entry.address))) {
    throw new ApiError(400, "invalid_payload", "Attachment url must resolve to a public address", { hostname });
  }
}

const resolveGmailAttachmentAddresses: GmailAttachmentAddressResolver = (hostname, options) => lookup(hostname, options);

function validateGmailAttachmentAddresses(hostname: string, addresses: LookupAddress[]): void {
  if (!addresses.length) throw new Error(`Attachment hostname ${hostname} did not resolve.`);
  const privateAddress = addresses.find((entry) => isLocalManagedMcpPrivateAddress(entry.address));
  if (privateAddress) {
    throw new Error(`Attachment hostname ${hostname} resolved to a private or reserved address (${privateAddress.address}).`);
  }
}

/**
 * Resolves and validates the attachment host inside the socket connector. The
 * same answers are handed to net.connect, closing the DNS-rebinding window
 * between a preflight lookup and the actual connection.
 */
export function createGmailAttachmentPublicLookup(
  resolver: GmailAttachmentAddressResolver = resolveGmailAttachmentAddresses,
): LookupFunction {
  return (hostname, options, callback) => {
    const lookupOptions: LookupAllOptions = { ...options, all: true, verbatim: true };
    void resolver(hostname, lookupOptions).then((addresses) => {
      try {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Retry the attachment download; transient DNS gaps usually resolve on a second attempt.
  2. Verify the hostname still has DNS records (dig) and fix or update the URL.
  3. Check any injected resolveGmailAttachmentAddresses override in tests — it must return at least one address.
  4. Increase resolver reliability (use a stable upstream resolver) if this recurs in production.

Example fix

// before (test override)
const resolver = async () => [];
// after
const resolver = async (h, o) => lookup(h, o); // return real addresses
Defensive patterns

Strategy: retry

Validate before calling

import { lookup } from "node:dns/promises";
async function hasDnsAnswers(hostname: string): Promise<boolean> {
  try { return (await lookup(hostname, { all: true, verbatim: true })).length > 0; } catch { return false; }
}

Try / catch

try {
  await fetchGmailAttachment({ url });
} catch (e) {
  if (e instanceof Error && /did not resolve\./.test(e.message)) {
    await sleep(1500);
    return fetchGmailAttachment({ url }); // DNS may have recovered between attempts
  }
  throw e;
}

Prevention

When it happens

Trigger: The GmailAttachmentAddressResolver callback yields [] for the hostname — i.e. zero DNS answers at connection setup, typically when the earlier lookup raced with a DNS change or the resolver was mocked/failed silently.

Common situations: DNS record removed between validation and connect (TOCTOU on DNS); custom resolver injected for tests returning empty results; network flakiness dropping all answers; very short TTL records expiring mid-flow.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/828c38e5bce7b626. Report an issue: GitHub.