paperclipai/paperclip · error · UnsafeChatPublicationError

External chat action id is invalid

Error message

External chat action id is invalid

What it means

For callback-type card actions, projectCard validates action.actionId against SAFE_IDENTIFIER_RE (/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/). A callback actionId is the opaque key echoed back when the user clicks the button, so it must be a short, safe identifier; anything empty, starting with a non-alphanumeric character, or containing whitespace, quotes, slashes, or other special characters (or longer than 160 chars) is rejected.

Solutions

  1. Generate actionIds as short slugs matching /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/ (e.g. "confirm-task-123"), not free-form text or URLs
  2. Encode structured data into the ID safely (base64url or a slugified key) instead of raw values with spaces/slashes
  3. Trim and sanitize the actionId at card-construction time; drop actions with invalid ids before projection
  4. Test the id with SAFE_IDENTIFIER_RE before building the callback action

Example fix

// before
{ type: "callback", actionId: `confirm ${task.title}`, label: "Confirm" }
// after
const slug = `confirm-task-${task.id}`.replace(/[^A-Za-z0-9_.:-]/g, "-");
if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/.test(slug)) throw new Error("bad actionId");
{ type: "callback", actionId: slug, label: "Confirm" }
Defensive patterns

Strategy: validation

Validate before calling

const SAFE_IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/;
if (typeof action.actionId !== "string" || !SAFE_IDENTIFIER_RE.test(action.actionId)) {
  throw new Error(`Invalid callback actionId: ${action.actionId}`);
}

Type guard

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

Try / catch

try {
  const payload = projectSafeChatPublication(input);
} catch (err) {
  if (err instanceof UnsafeChatPublicationError && err.message === "External chat action id is invalid") {
    logger.error("callback actionId failed SAFE_IDENTIFIER_RE", { card: input.interaction.card });
    return null; // skip the publication or rebuild with slugified ids
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing a callback action with actionId that is null/undefined at runtime, empty, starts with a symbol (e.g. ".foo", "-foo"), contains spaces or slashes (e.g. "issue/123 confirm"), has non-ASCII characters, or exceeds 160 characters.

Common situations: Using a raw URL path segment or a sentence as the actionId; embedding a UUID with braces; passing user-controlled text as the actionId; forgetting to slugify a label used as an identifier; a type-level mismatch where actionId is optional but the code treats it as required.

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/7c3b681c4a51e921. Report an issue: GitHub.

Appendix: source

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

  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`,
    );
  }

  const actions: SafeExternalChatCardAction[] = [];
  for (const action of rawActions) {
    const label = truncateByCodePoint(
      projectSafeChatPublicationText(action.label),
      MAX_ACTION_LABEL_LENGTH,
    );
    if (action.type === "callback") {
      if (!SAFE_IDENTIFIER_RE.test(action.actionId)) {
        throw new UnsafeChatPublicationError(
          "External chat action id is invalid",
        );
      }
      if (action.style && !CARD_ACTION_STYLES.has(action.style)) {
        throw new UnsafeChatPublicationError(
          "External chat action style is invalid",
        );
      }
      actions.push({
        type: "callback",
        actionId: action.actionId,
        label,
        ...(action.style ? { style: action.style } : {}),
      });
      continue;
    }

    if (action.type !== "link") {

View on GitHub (pinned to 3f1d897a7c)