can1357/oh-my-pi · error

Discord attachment did not include a URL

Error message

Discord attachment did not include a URL

What it means

Thrown by parseMessage() when Discord's response has a message id and a non-empty attachments array, but attachments[0].url is missing or not a string. The uploader needs this CDN URL as the publication URL for the uploaded blob. Discord message attachment objects always carry a `url` in normal operation, so its absence signals an abnormal or hostile response shape.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders-discord.ts:61

		throw new Error("Discord webhook credential does not contain a webhook ID and token");
	}
	return { id, token };
}

function webhookEndpoint(webhook: DiscordWebhook, suffix?: string): URL {
	const base = `${DISCORD_API_ORIGIN}/api/v10/webhooks/${encodeURIComponent(webhook.id)}/${encodeURIComponent(webhook.token)}`;
	return new URL(suffix ? `${base}/${suffix}` : base);
}

function parseMessage(value: unknown): DiscordMessage {
	if (!value || typeof value !== "object") throw new Error("Discord returned an invalid message response");
	const message = value as Record<string, unknown>;
	if (typeof message.id !== "string") throw new Error("Discord response did not include a message ID");
	if (!Array.isArray(message.attachments)) throw new Error("Discord response did not include an attachment");
	const first = message.attachments[0];
	if (!first || typeof first !== "object") throw new Error("Discord response did not include an attachment");
	const attachmentUrl = (first as Record<string, unknown>).url;
	if (typeof attachmentUrl !== "string") throw new Error("Discord attachment did not include a URL");
	try {
		const parsed = new URL(attachmentUrl);
		if (parsed.protocol !== "https:") throw new Error();
	} catch {
		throw new Error("Discord attachment URL is invalid");
	}
	return { id: message.id, attachmentUrl };
}

function attachmentExpiry(url: string, now: number): number {
	const signedExpiry = new URL(url).searchParams.get("ex");
	if (signedExpiry && /^[0-9a-f]+$/i.test(signedExpiry)) {
		const seconds = Number.parseInt(signedExpiry, 16);
		const expiresAt = seconds * 1_000;
		if (Number.isSafeInteger(expiresAt) && expiresAt > 0) return expiresAt;
	}
	return now + FALLBACK_LIFETIME_MS;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Log the full attachments[0] object from the response to see which fields Discord returned.
  2. Verify requests go directly to https://discord.com/api/v10 with no reshaping proxy.
  3. Pin/verify the API version (v10) and check Discord's changelog for attachment schema changes.
  4. Retry the upload; partial records are often transient.
  5. If persistently reproducible, capture the response and report/check Discord status.
Defensive patterns

Strategy: type-guard

Type guard

function attachmentHasUrl(v: unknown): v is { url: string } {
	return !!v && typeof v === "object" && typeof (v as Record<string, unknown>).url === "string";
}

Try / catch

try {
	const message = parseMessage(await response.json());
} catch (err) {
	if (err instanceof Error && err.message.includes("did not include a URL")) {
		// capture attachments[0] for diagnosis; fall back to another destination or retry
	}
	throw err;
}

Prevention

When it happens

Trigger: attachments[0] is an object lacking a `url` string field — e.g. a partial attachment record from a degraded API response, a proxy returning trimmed JSON, or Discord schema changes in preview/experimental API versions.

Common situations: Using an unofficial proxy of the Discord API that strips fields; Discord schema drift; attaching via a thread the uploader cannot fully resolve; intercepting fetch wrappers that cache and reshape responses.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/7395f26921a83fa5. Report an issue: GitHub.