paperclipai/paperclip · error · UnsafeChatPublicationError

External chat interaction id is invalid

Error message

External chat interaction id is invalid

What it means

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.

Solutions

  1. 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>')
  2. Sanitize the id: replace disallowed characters with '-' and trim to 160 chars, ensuring the first char is alphanumeric
  3. Validate the id with SAFE_IDENTIFIER_RE before constructing the interaction input
  4. Pass a stable internal id (issue id, interaction record id) instead of a composite human-readable label

Example fix

// before
interaction: { id: `issues/${issue.id}/confirm`, card: {...} }
// after
interaction: { id: `confirm-${issue.id}`, card: {...} } // matches SAFE_IDENTIFIER_RE
Defensive patterns

Strategy: type-guard

Validate before calling

const SAFE_IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/;
if (!SAFE_IDENTIFIER_RE.test(interaction.id)) throw new TypeError(`Invalid interaction id: ${interaction.id}`);

Type guard

function isValidInteractionId(id: unknown): id is string {
  return typeof id === "string" && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/.test(id);
}

Try / catch

try {
  const payload = projectSafeChatPublication({ classification: "external", source, text, interaction });
} catch (err) {
  if (err instanceof UnsafeChatPublicationError && /interaction id is invalid/.test(err.message)) {
    console.error("interaction.id must match ^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$", { id: interaction?.id });
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

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

    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",
    );
  }
  if (!CARD_KINDS.has(input.card.kind)) {
    throw new UnsafeChatPublicationError("External chat card kind is invalid");
  }

  const title = truncateByCodePoint(
    projectSafeChatPublicationText(input.card.title),
    MAX_TITLE_LENGTH,
  );
  const body = input.card.body
    ? projectSafeChatPublicationText(input.card.body)
    : undefined;
  const rawActions = input.card.actions ?? [];
  if (rawActions.length > MAX_CARD_ACTIONS) {
    throw new UnsafeChatPublicationError(
      `External chat cards support at most ${MAX_CARD_ACTIONS} actions`,

View on GitHub (pinned to 3f1d897a7c)