paperclipai/paperclip · error · UnsafeChatPublicationError

External chat publications support at most

Error message

External chat publications support at most ${MAX_ATTACHMENTS} attachments

What it means

projectAttachmentIds validates the attachmentIds array of a chat publication before it is sent to an external provider. It enforces MAX_ATTACHMENTS (20, chat-publication-projection.ts:13). If more than 20 attachment ids are supplied it throws UnsafeChatPublicationError, because external chat publications are capped at 20 attachments. The projection is the single boundary that accepts attachments, so this check guards the provider-bound payload.

Solutions

  1. Trim attachmentIds to the 20 most relevant before calling projectSafeChatPublication
  2. Batch the publication: send attachments in chunks of at most 20 across multiple publications
  3. Attach a single bundle (zip) instead of many individual files
  4. Fix upstream accumulation code that appends ids without a limit

Example fix

// before
await publish({ classification: "external", source: "explicit_board_send", text, attachmentIds: allIds });
// after
const MAX_ATTACHMENTS = 20;
await publish({
  classification: "external",
  source: "explicit_board_send",
  text,
  attachmentIds: allIds.slice(0, MAX_ATTACHMENTS),
});
Defensive patterns

Strategy: validation

Validate before calling

const MAX_ATTACHMENTS = 20;
if (attachmentIds && attachmentIds.length > MAX_ATTACHMENTS) {
  attachmentIds = attachmentIds.slice(0, MAX_ATTACHMENTS);
}

Type guard

function isWithinAttachmentLimit(ids: readonly string[] | null | undefined): ids is readonly string[] {
  return !!ids && ids.length <= 20;
}

Try / catch

try {
  const payload = projectSafeChatPublication({ classification: "external", source, text, attachmentIds });
} catch (err) {
  if (err instanceof UnsafeChatPublicationError && /at most 20 attachments/.test(err.message)) {
    const payload = projectSafeChatPublication({ classification: "external", source, text, attachmentIds: attachmentIds!.slice(0, 20) });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling projectSafeChatPublication with attachmentIds containing 21 or more string ids. Examples: an agent comment attaching every file touched in a large change set; a loop that accumulates attachment ids without a cap; an API client passing an unfiltered list of uploaded asset ids.

Common situations: An automation attaches all artifacts from a long-running task; a migration script forwards a legacy issue's full attachment history; a UI bulk-select that has no client-side limit sends 30 screenshots with one publication.

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


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/bb771698d2231c25. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/chat-publication-projection.ts:278

    .replace(/[ \t]+\n/g, "\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim();

  if (!output) return "Update available in Paperclip.";
  if (output.length > MAX_TEXT_OUTPUT_LENGTH) {
    throw new UnsafeChatPublicationError(
      "External chat text exceeds its projected processing limit",
    );
  }
  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);
    }
  }

View on GitHub (pinned to 3f1d897a7c)