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
- 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>')
- Sanitize the id: replace disallowed characters with '-' and trim to 160 chars, ensuring the first char is alphanumeric
- Validate the id with SAFE_IDENTIFIER_RE before constructing the interaction input
- 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
- Derive interaction ids from stable internal identifiers (issue UUID, record id)
- Never use paths, URLs, spaces, or encoded blobs as interaction ids
- Sanitize composite ids: strip non [A-Za-z0-9_.:-] characters and cap at 160 chars
- Share a single SAFE_IDENTIFIER validation helper across code that builds interactions
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
- CreateOS returned an invalid resource ID.
- device-login promotion: the account identifier cannot form…
- External chat attachment ids must be UUIDs
- External chat card kind is invalid
- github_webhook_recovery_invalid_input
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)