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
- 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
- Encode structured data into the ID safely (base64url or a slugified key) instead of raw values with spaces/slashes
- Trim and sanitize the actionId at card-construction time; drop actions with invalid ids before projection
- 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
- Always generate actionIds from structured keys (ids, enums), never free text or URLs
- Slugify any user-derived component: replace(/[^A-Za-z0-9_.:-]/g, "-") and trim leading non-alphanumerics
- Validate the full card with a zod schema before calling the projection API
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
- Chat publication source must be explicitly classified for…
- External chat action style is invalid
- External chat card action type is invalid
- External chat cards support at most
- Invalid project repository directory
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)