coleam00/Archon · warning

Invalid multipart form data

Error message

Invalid multipart form data

What it means

The conversation-message route received a multipart/form-data request but Hono's c.req.parseBody({ all: true }) threw while parsing it. The server logs upload.parse_failed (with the parse error and conversationId) and returns 400, meaning the request body is not well-formed multipart data the framework can decode.

Source

Thrown at packages/server/src/routes/api.ts:2893

    // Reject conversation IDs that could be used for path traversal when building
    // the upload directory. Web conversation IDs are alphanumeric with hyphens only.
    if (!/^[\w-]+$/.test(conversationId)) {
      return c.json({ error: 'Invalid conversation ID' }, 400);
    }

    let message: string;
    let savedFiles: AttachedFile[] = [];
    let uploadDir = '';

    const contentType = c.req.header('content-type') ?? '';

    if (contentType.includes('multipart/form-data')) {
      let body: Record<string, string | File | (string | File)[]>;
      try {
        body = await c.req.parseBody({ all: true });
      } catch (parseErr: unknown) {
        getLog().warn({ err: parseErr, conversationId }, 'upload.parse_failed');
        return c.json({ error: 'Invalid multipart form data' }, 400);
      }

      const rawMessage = body.message;
      if (typeof rawMessage !== 'string' || !rawMessage) {
        return c.json({ error: 'message must be a non-empty string' }, 400);
      }
      message = rawMessage;

      const rawFiles = body.files;
      let fileList: (string | File)[];
      if (Array.isArray(rawFiles)) {
        fileList = rawFiles;
      } else if (rawFiles !== undefined) {
        fileList = [rawFiles];
      } else {
        fileList = [];
      }

View on GitHub (pinned to 0773b97458)

Solutions

  1. Rebuild the request with a proper multipart encoder (FormData in fetch/undici, -F in curl) instead of hand-writing the body.
  2. Ensure the Content-Type header includes the exact boundary generated by the encoder (do not set Content-Type manually with fetch + FormData).
  3. Verify the body is fully transmitted (Content-Length correct, no proxy truncation) and the file fields are valid.
  4. Log the raw body once in a test to confirm it is valid multipart before blaming the server.

Example fix

// before: hand-built multipart
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'multipart/form-data; boundary=xyz' }, body: str });
// after: let the encoder set the boundary
const form = new FormData();
form.append('message', 'hi');
form.append('file', new File([buf], 'a.txt'));
const res = await fetch(url, { method: 'POST', body: form });
Defensive patterns

Strategy: validation

Validate before calling

// client-side: build a real multipart body, never hand-write it
const form = new FormData();
form.append('message', message);
for (const f of files) form.append('files', f, f.name);
if (!message || typeof message !== 'string') throw new Error('message must be a non-empty string');
// do NOT set Content-Type manually; fetch adds the correct boundary

Type guard

function looksMultipart(header: string | null): boolean {
  return !!header && /multipart\/form-data;\s*boundary=/.test(header);
}
// verify the request you are about to send:
// looksMultipart(req.headers.get('content-type'))

Try / catch

try {
  const res = await sendUpload(form);
  if (res.status === 400 && (await res.json()).error === 'Invalid multipart form data') {
    throw new Error('Client produced a malformed multipart body — fix the encoder, not the payload');
  }
} catch (err) { /* log full request headers + body length for diagnosis */ }

Prevention

When it happens

Trigger: Sending multipart/form-data to the conversation message endpoint with a malformed body: missing/mismatched boundary, truncated body, hand-rolled multipart string instead of a real encoder, wrong Content-Length, or a Content-Type header whose boundary does not match the body.

Common situations: Custom HTTP clients/curl scripts constructing multipart manually; proxies or gateways truncating large uploads; SDK version mismatch producing a body/boundary mismatch; testing with an incomplete fetch where the File field was never appended.

Related errors


AI-assisted analysis of coleam00/Archon@0773b97458 (2026-09-01). Data as JSON: /api/errors/b0a3b1c61657f6e6. Report an issue: GitHub.