paperclipai/paperclip · error · UnsafeChatPublicationError

External chat attachment ids must be UUIDs

Error message

External chat attachment ids must be UUIDs

What it means

projectAttachmentIds normalizes each attachment id (trim + lowercase) and requires it to match UUID_RE (chat-publication-projection.ts:18), a versioned RFC-4122-style UUID pattern. Any id that is not a UUID causes UnsafeChatPublicationError. Attachment ids reference Paperclip's internal attachment records, which are UUIDs, so non-UUID values indicate a malformed or foreign identifier being smuggled into the provider-bound payload.

Solutions

  1. Ensure every attachment id comes from the Paperclip attachment record's UUID id field, not a filename or numeric key
  2. Validate ids client-side with a UUID regex before building the publication input
  3. Trim ids and reject empty strings before passing the array
  4. Trace where the malformed id originates (legacy data, external provider id, or concatenation bug) and fix the source

Example fix

// before
attachmentIds: files.map((f) => f.name),
// after
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
attachmentIds: files.map((f) => f.id).filter((id) => UUID_RE.test(id.trim().toLowerCase())),
Defensive patterns

Strategy: type-guard

Validate before calling

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const invalid = (attachmentIds ?? []).filter((id) => !UUID_RE.test(id.trim().toLowerCase()));
if (invalid.length) throw new TypeError(`Non-UUID attachment ids: ${invalid.join(", ")}`);

Type guard

function isAttachmentId(value: unknown): value is string {
  return typeof value === "string" &&
    /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value.trim().toLowerCase());
}

Try / catch

try {
  const payload = projectSafeChatPublication({ classification: "external", source, text, attachmentIds });
} catch (err) {
  if (err instanceof UnsafeChatPublicationError && /must be UUIDs/.test(err.message)) {
    console.error("attachmentIds contain non-UUID values; check filename-vs-id mixups", { attachmentIds });
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing an attachment id that is a numeric database id, a filename, a URL, a slug, or an arbitrary string instead of a UUID string like '550e8400-e29b-41d4-a716-446655440000'. Also triggered by ids with stray whitespace that remain non-UUID after trim, or empty strings in the array.

Common situations: A caller confuses attachment filenames with attachment ids; older records use integer keys from a pre-UUID schema; a client builds ids by concatenation ('att-' + number); an integration passes a Slack file id instead of a Paperclip attachment id.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/bac078e362221bcf. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/chat-publication-projection.ts:288

  return output;
}

function projectAttachmentIds(
  input: readonly string[] | null | undefined,
): string[] | undefined {
  if (!input?.length) return undefined;
  if (input.length > MAX_ATTACHMENTS) {
    throw new UnsafeChatPublicationError(
      `External chat publications support at most ${MAX_ATTACHMENTS} attachments`,
    );
  }

  const output: string[] = [];
  const seen = new Set<string>();
  for (const id of input) {
    const normalized = id.trim().toLowerCase();
    if (!UUID_RE.test(normalized)) {
      throw new UnsafeChatPublicationError(
        "External chat attachment ids must be UUIDs",
      );
    }
    if (!seen.has(normalized)) {
      seen.add(normalized);
      output.push(normalized);
    }
  }
  return output.length ? output : undefined;
}

function projectCard(
  input: NonNullable<ChatPublicationProjectionInput["interaction"]>,
): { interactionId: string; card: SafeExternalChatCard } {
  if (!SAFE_IDENTIFIER_RE.test(input.id)) {
    throw new UnsafeChatPublicationError(
      "External chat interaction id is invalid",
    );

View on GitHub (pinned to 3f1d897a7c)