paperclipai/paperclip · error · UnsafeChatPublicationError
External chat attachment ids must be UUIDs
Error message
External chat attachment ids must be UUIDs
What it means
projectAttachmentIds normalizes each attachment id (trim + lowercase) and requires it to match UUID_RE (chat-publication-projection.ts:18), a versioned RFC-4122-style UUID pattern. Any id that is not a UUID causes UnsafeChatPublicationError. Attachment ids reference Paperclip's internal attachment records, which are UUIDs, so non-UUID values indicate a malformed or foreign identifier being smuggled into the provider-bound payload.
Solutions
- Ensure every attachment id comes from the Paperclip attachment record's UUID id field, not a filename or numeric key
- Validate ids client-side with a UUID regex before building the publication input
- Trim ids and reject empty strings before passing the array
- Trace where the malformed id originates (legacy data, external provider id, or concatenation bug) and fix the source
Example fix
// before
attachmentIds: files.map((f) => f.name),
// after
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
attachmentIds: files.map((f) => f.id).filter((id) => UUID_RE.test(id.trim().toLowerCase())), Defensive patterns
Strategy: type-guard
Validate before calling
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const invalid = (attachmentIds ?? []).filter((id) => !UUID_RE.test(id.trim().toLowerCase()));
if (invalid.length) throw new TypeError(`Non-UUID attachment ids: ${invalid.join(", ")}`); Type guard
function isAttachmentId(value: unknown): value is string {
return typeof value === "string" &&
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value.trim().toLowerCase());
} Try / catch
try {
const payload = projectSafeChatPublication({ classification: "external", source, text, attachmentIds });
} catch (err) {
if (err instanceof UnsafeChatPublicationError && /must be UUIDs/.test(err.message)) {
console.error("attachmentIds contain non-UUID values; check filename-vs-id mixups", { attachmentIds });
}
throw err;
} Prevention
- Always take ids from the attachment record's id field, never filenames or provider file ids
- Type attachment id arrays as `${string}` branded UUID types where possible
- Filter with a UUID regex at the boundary where ids are collected
- Beware legacy integer-key records: migrate or exclude them from external publications
When it happens
Trigger: Passing an attachment id that is a numeric database id, a filename, a URL, a slug, or an arbitrary string instead of a UUID string like '550e8400-e29b-41d4-a716-446655440000'. Also triggered by ids with stray whitespace that remain non-UUID after trim, or empty strings in the array.
Common situations: A caller confuses attachment filenames with attachment ids; older records use integer keys from a pre-UUID schema; a client builds ids by concatenation ('att-' + number); an integration passes a Slack file id instead of a Paperclip attachment id.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- --api-key-secret-id must be a UUID
- Attachment exceeds the configured size limit
- Completion cites no registered attachment on this task. Use…
- External chat interaction id is invalid
- External chat publications support at most
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/bac078e362221bcf.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/chat-publication-projection.ts:288
return output;
}
function projectAttachmentIds(
input: readonly string[] | null | undefined,
): string[] | undefined {
if (!input?.length) return undefined;
if (input.length > MAX_ATTACHMENTS) {
throw new UnsafeChatPublicationError(
`External chat publications support at most ${MAX_ATTACHMENTS} attachments`,
);
}
const output: string[] = [];
const seen = new Set<string>();
for (const id of input) {
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",
);View on GitHub (pinned to 3f1d897a7c)