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
- Retry the attachment download; transient DNS gaps usually resolve on a second attempt.
- Verify the hostname still has DNS records (dig) and fix or update the URL.
- Check any injected resolveGmailAttachmentAddresses override in tests — it must return at least one address.
- 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
- In tests, never inject an address resolver that returns an empty array.
- Retry on transient DNS failures with small backoff.
- Monitor DNS record TTLs for attachment hosts.
- Confirm hostname still exists if the error repeats.
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
- attachment_fetch_failed
- uploadErrorMessage(metadata.filename, error)
- the hostname does not resolve
- latest-mac.yml is missing artifact path/url.
- Failed to fetch latest-mac.yml (${response.status} ${respons
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/828c38e5bce7b626.
Report an issue: GitHub.