can1357/oh-my-pi · error
Discord response did not include an attachment
Error message
Discord response did not include an attachment
What it means
Thrown by parseMessage() when the Discord webhook response contains a valid message `id` but `attachments` is missing or is not an array. The uploader uploads a file via multipart and requires the resulting message to carry at least one attachment object, from which it extracts the CDN URL for the published blob. Its absence means Discord did not process the file as an attachment.
Source
Thrown at packages/coding-agent/src/blob-broker/uploaders-discord.ts:57
const webhooksIndex = segments.indexOf("webhooks");
const id = webhooksIndex >= 0 ? segments[webhooksIndex + 1] : undefined;
const token = webhooksIndex >= 0 ? segments[webhooksIndex + 2] : undefined;
if (!id || !token || !/^\d+$/.test(id)) {
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;View on GitHub (pinned to 9690622007)
Solutions
- Verify payload_json attachments[{id:0, filename}] matches the multipart field name files[0] and the declared filename exactly.
- Log the response body to inspect what Discord returned instead of an attachments array.
- Check the file size/content-type against Discord's webhook upload limit (8–25MB depending on tier).
- Retry the upload — transient Discord issues can drop attachments.
- Test the webhook manually with curl using the same multipart payload to isolate the uploader.
Defensive patterns
Strategy: validation
Validate before calling
function hasAttachments(body: unknown): boolean {
return !!body && typeof body === "object" && Array.isArray((body as Record<string, unknown>).attachments);
}
// call: if (!hasAttachments(await response.clone().json())) throw new Error("discord dropped attachment"); Type guard
function hasAttachmentArray(v: unknown): v is { attachments: Record<string, unknown>[] } {
return !!v && typeof v === "object" && Array.isArray((v as Record<string, unknown>).attachments);
} Try / catch
try {
const message = parseMessage(await response.json());
} catch (err) {
if (err instanceof Error && err.message.includes("attachment")) {
// inspect payload_json/files[0] pairing, then retry once
}
throw err;
} Prevention
- Keep payload_json attachments[{id:0,...}] index in sync with the files[0] multipart field name.
- Check file size against Discord's webhook upload limit before uploading.
- Never send a zero-byte or unreadable file stream.
- Test webhook uploads manually with curl when configuring a new destination.
When it happens
Trigger: Webhook POST with files[0] multipart body returned a message object without an attachments array — e.g. Discord accepted the message but dropped the file, payload_json mismatched the files[] indices, a proxy downgraded the request, or a thread_id pointed somewhere attachments were not returned.
Common situations: payload_json attachment metadata (id/filename) mismatched with files[0], causing Discord to discard the file; upload size rejected but message still posted (text-only content); API behavior changes in newer Discord webhook versions; intermediaries stripping multipart file parts.
Related errors
- Discord attachment did not include a URL
- Discord response did not include a message ID
- Discord webhook credential is not a valid URL
- Discord webhook credential must use HTTPS
- Discord webhook credential does not contain a webhook ID and
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/63a7207d0d9e8fad.
Report an issue: GitHub.