paperclipai/paperclip · error · UnsafeChatPublicationError
External chat action style is invalid
Error message
External chat action style is invalid
What it means
For callback-type card actions, an optional action.style must be one of the allowed CARD_ACTION_STYLES values: "default", "primary", or "danger". Any other string (or an invalid runtime value) throws UnsafeChatPublicationError, because the style is forwarded verbatim to the external chat provider payload.
Solutions
- Use only "default", "primary", or "danger" for action.style (matching is exact and case-sensitive)
- Map your app's severity/style vocabulary to the allowed set before constructing the card
- Omit the style field entirely when no explicit emphasis is needed (it is optional)
- Add a whitelist check on style where the card is built
Example fix
// before style: severity === "critical" ? "alert" : "normal" // after style: severity === "critical" ? "danger" : "default" // one of "default"|"primary"|"danger"
Defensive patterns
Strategy: type-guard
Validate before calling
const CARD_ACTION_STYLES = new Set(["default", "primary", "danger"]);
if (action.style !== undefined && !CARD_ACTION_STYLES.has(action.style)) {
action.style = undefined; // or normalize to "default"
} Type guard
type CardActionStyle = "default" | "primary" | "danger";
function isCardActionStyle(v: unknown): v is CardActionStyle {
return v === "default" || v === "primary" || v === "danger";
} Try / catch
try {
const payload = projectSafeChatPublication(input);
} catch (err) {
if (err instanceof UnsafeChatPublicationError && err.message === "External chat action style is invalid") {
for (const a of input.interaction.card.actions) {
if (a.type === "callback" && !isCardActionStyle(a.style)) delete a.style;
}
return projectSafeChatPublication(input);
}
throw err;
} Prevention
- Define the style union as a shared TypeScript type so invalid values fail at compile time
- Map external design-system severities to {default, primary, danger} at the boundary
- Omit style rather than guessing a value when no emphasis is intended
When it happens
Trigger: Setting a callback action's style to a value outside {"default","primary","danger"} — e.g. "secondary", "destructive", "warning", "DANGER" (case-sensitive), or a value typed from another design system's button-style enum.
Common situations: Reusing Slack/Teams button style enums (e.g. "primary" vs "danger" naming differences) directly in the card; mapping a UI severity like "critical" to a card style; a typo or casing mismatch after a rename.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- ACPX identity permission mode is invalid
- Chat publication source must be explicitly classified for…
- eval-session acpxAgent must be codex or claude
- Expected ACPX permission mode is invalid
- External chat action id is invalid
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/8692f3d36ab0fe7c.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/chat-publication-projection.ts:339
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") {
throw new UnsafeChatPublicationError(
"External chat card action type is invalid",
);
}
View on GitHub (pinned to 3f1d897a7c)