{"record":{"id":"bac078e362221bcf","repo":"paperclipai/paperclip","slug":"external-chat-attachment-ids-must-be-uuids","errorCode":null,"errorMessage":"External chat attachment ids must be UUIDs","messagePattern":"External chat attachment ids must be UUIDs","errorType":"validation","errorClass":"UnsafeChatPublicationError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-publication-projection.ts","lineNumber":288,"sourceCode":"  return output;\n}\n\nfunction projectAttachmentIds(\n  input: readonly string[] | null | undefined,\n): string[] | undefined {\n  if (!input?.length) return undefined;\n  if (input.length > MAX_ATTACHMENTS) {\n    throw new UnsafeChatPublicationError(\n      `External chat publications support at most ${MAX_ATTACHMENTS} attachments`,\n    );\n  }\n\n  const output: string[] = [];\n  const seen = new Set<string>();\n  for (const id of input) {\n    const normalized = id.trim().toLowerCase();\n    if (!UUID_RE.test(normalized)) {\n      throw new UnsafeChatPublicationError(\n        \"External chat attachment ids must be UUIDs\",\n      );\n    }\n    if (!seen.has(normalized)) {\n      seen.add(normalized);\n      output.push(normalized);\n    }\n  }\n  return output.length ? output : undefined;\n}\n\nfunction projectCard(\n  input: NonNullable<ChatPublicationProjectionInput[\"interaction\"]>,\n): { interactionId: string; card: SafeExternalChatCard } {\n  if (!SAFE_IDENTIFIER_RE.test(input.id)) {\n    throw new UnsafeChatPublicationError(\n      \"External chat interaction id is invalid\",\n    );","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/chat-publication-projection.ts#L270-L306","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure every attachment id comes from the Paperclip attachment record's UUID id field, not a filename or numeric key","Validate ids client-side with a UUID regex before building the publication input","Trim ids and reject empty strings before passing the array","Trace where the malformed id originates (legacy data, external provider id, or concatenation bug) and fix the source"],"exampleFix":"// before\nattachmentIds: files.map((f) => f.name),\n// after\nconst 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;\nattachmentIds: files.map((f) => f.id).filter((id) => UUID_RE.test(id.trim().toLowerCase())),","handlingStrategy":"type-guard","validationCode":"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;\nconst invalid = (attachmentIds ?? []).filter((id) => !UUID_RE.test(id.trim().toLowerCase()));\nif (invalid.length) throw new TypeError(`Non-UUID attachment ids: ${invalid.join(\", \")}`);","typeGuard":"function isAttachmentId(value: unknown): value is string {\n  return typeof value === \"string\" &&\n    /^[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());\n}","tryCatchPattern":"try {\n  const payload = projectSafeChatPublication({ classification: \"external\", source, text, attachmentIds });\n} catch (err) {\n  if (err instanceof UnsafeChatPublicationError && /must be UUIDs/.test(err.message)) {\n    console.error(\"attachmentIds contain non-UUID values; check filename-vs-id mixups\", { attachmentIds });\n  }\n  throw err;\n}","preventionTips":["Always take ids from the attachment record's id field, never filenames or provider file ids","Type attachment id arrays as `${string}` branded UUID types where possible","Filter with a UUID regex at the boundary where ids are collected","Beware legacy integer-key records: migrate or exclude them from external publications"],"tags":["validation","uuid","attachments","format"],"backgroundTag":"invalid-argument-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}