{"record":{"id":"bdad0fb163646e0b","repo":"paperclipai/paperclip","slug":"external-chat-interaction-id-is-invalid","errorCode":null,"errorMessage":"External chat interaction id is invalid","messagePattern":"External chat interaction id is invalid","errorType":"validation","errorClass":"UnsafeChatPublicationError","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-publication-projection.ts","lineNumber":304,"sourceCode":"    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    );\n  }\n  if (!CARD_KINDS.has(input.card.kind)) {\n    throw new UnsafeChatPublicationError(\"External chat card kind is invalid\");\n  }\n\n  const title = truncateByCodePoint(\n    projectSafeChatPublicationText(input.card.title),\n    MAX_TITLE_LENGTH,\n  );\n  const body = input.card.body\n    ? projectSafeChatPublicationText(input.card.body)\n    : undefined;\n  const rawActions = input.card.actions ?? [];\n  if (rawActions.length > MAX_CARD_ACTIONS) {\n    throw new UnsafeChatPublicationError(\n      `External chat cards support at most ${MAX_CARD_ACTIONS} actions`,","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/chat-publication-projection.ts#L286-L322","documentation":"projectCard validates the interaction block of a chat publication (an interactive card delivered to an external provider). The interaction id must match SAFE_IDENTIFIER_RE (chat-publication-projection.ts:20): 1-160 characters starting with an alphanumeric, then only [A-Za-z0-9_.:-]. An id that is empty, exceeds 160 chars, starts with a non-alphanumeric, or contains characters outside that set throws UnsafeChatPublicationError. This keeps interaction ids safe to round-trip through provider callback payloads.","triggerScenarios":"Calling projectSafeChatPublication with interaction.id that is: a full UUID (invalid because '-' is allowed, but UUIDs with braces or uppercase are fine — actually '-' IS allowed, so failures come from spaces, slashes, '#', '<', quotes), an empty string, a string longer than 160 characters, one starting with '_' or '.', or one containing URL-unsafe characters like '/', '?', or whitespace.","commonSituations":"A developer uses a generated token, JSON blob, or base64 string as the interaction id; an id is built by joining issue number + title with spaces; a newline-containing id from user input is passed through; a template interpolates a path like 'issues/123/comments' as the id.","solutions":["Use a short slug identifier matching /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/ (e.g. the Paperclip issue UUID or 'confirm-<issueId>')","Sanitize the id: replace disallowed characters with '-' and trim to 160 chars, ensuring the first char is alphanumeric","Validate the id with SAFE_IDENTIFIER_RE before constructing the interaction input","Pass a stable internal id (issue id, interaction record id) instead of a composite human-readable label"],"exampleFix":"// before\ninteraction: { id: `issues/${issue.id}/confirm`, card: {...} }\n// after\ninteraction: { id: `confirm-${issue.id}`, card: {...} } // matches SAFE_IDENTIFIER_RE","handlingStrategy":"type-guard","validationCode":"const SAFE_IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/;\nif (!SAFE_IDENTIFIER_RE.test(interaction.id)) throw new TypeError(`Invalid interaction id: ${interaction.id}`);","typeGuard":"function isValidInteractionId(id: unknown): id is string {\n  return typeof id === \"string\" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/.test(id);\n}","tryCatchPattern":"try {\n  const payload = projectSafeChatPublication({ classification: \"external\", source, text, interaction });\n} catch (err) {\n  if (err instanceof UnsafeChatPublicationError && /interaction id is invalid/.test(err.message)) {\n    console.error(\"interaction.id must match ^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$\", { id: interaction?.id });\n  }\n  throw err;\n}","preventionTips":["Derive interaction ids from stable internal identifiers (issue UUID, record id)","Never use paths, URLs, spaces, or encoded blobs as interaction ids","Sanitize composite ids: strip non [A-Za-z0-9_.:-] characters and cap at 160 chars","Share a single SAFE_IDENTIFIER validation helper across code that builds interactions"],"tags":["validation","identifier","format","chat-card"],"backgroundTag":"invalid-identifier-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}