paperclipai/paperclip · error · UnsafeChatPublicationError
External chat cards support at most
Error message
External chat cards support at most ${MAX_CARD_ACTIONS} actions What it means
projectCard enforces a hard cap of MAX_CARD_ACTIONS (12) actions on interaction cards projected for external chat delivery (Slack etc.). If input.card.actions contains more than 12 entries, it throws UnsafeChatPublicationError instead of silently truncating. The cap bounds the size and complexity of what gets posted to third-party providers.
Solutions
- Reduce card.actions to at most 12 entries before calling projectSafeChatPublication (keep the most important actions, or drop links first)
- Split the content across multiple publications or move the overflow actions into the card body text as plain links
- Check the actions length at card-construction time and fail early where the list is built, rather than at projection time
Example fix
// before
const actions = allOptions.map((o) => ({ type: "link", label: o.label, url: o.url }));
// after
const actions = allOptions.slice(0, 12).map((o) => ({ type: "link", label: o.label, url: o.url })); Defensive patterns
Strategy: validation
Validate before calling
const MAX_CARD_ACTIONS = 12;
if ((card.actions ?? []).length > MAX_CARD_ACTIONS) {
card.actions = card.actions.slice(0, MAX_CARD_ACTIONS); // or throw early
} Type guard
function hasAcceptableActionCount(card: { actions?: unknown[] }): boolean {
return (card.actions?.length ?? 0) <= 12;
} Try / catch
try {
const payload = projectSafeChatPublication(input);
} catch (err) {
if (err instanceof UnsafeChatPublicationError && /at most \d+ actions/.test(err.message)) {
input.interaction.card.actions = input.interaction.card.actions.slice(0, 12);
return projectSafeChatPublication(input);
}
throw err;
} Prevention
- Cap action lists where the card is constructed, not at projection time
- Prefer links in the body text when the action set is dynamic and potentially large
- Write a unit test asserting cards never exceed 12 actions
When it happens
Trigger: Calling projectSafeChatPublication (or the interaction projection path that calls projectCard) with input.interaction.card.actions containing 13 or more action objects (callback or link actions combined).
Common situations: Building a rich confirmation or question card that programmatically appends an action per option/task/label and ends up with more than 12 buttons; concatenating several card action lists; a UI feature that generates one action per issue link without limiting the count.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Chat publication source must be explicitly classified for…
- External chat action id is invalid
- External chat action style is invalid
- External chat card action type is invalid
- External chat publications support at most
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/98edbfc9855613db.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/chat-publication-projection.ts:321
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`,
);
}
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(View on GitHub (pinned to 3f1d897a7c)