paperclipai/paperclip · error · UnsafeChatPublicationError

External chat text exceeds its processing limit

Error message

External chat text exceeds its processing limit

What it means

projectSafeChatPublicationText is the only text path allowed from Paperclip into an external chat provider, and it bounds its sanitization work by rejecting inputs over MAX_TEXT_INPUT_LENGTH (1,000,000 UTF-16 units) with an UnsafeChatPublicationError. The limit protects against pathological redaction/regex workloads; it bounds processing, not delivery — shorter text may still expand during redaction and that is allowed.

Solutions

  1. Truncate or summarize the text below 1,000,000 characters before projecting (keep the most recent/relevant portion).
  2. Split the content into multiple smaller chat messages.
  3. Attach large content as files/attachments (MAX_ATTACHMENTS = 20) instead of inline text.
  4. If this happens systematically, fix the producer that concatenates unbounded content into one publication.

Example fix

// before
await enqueueResponse(issueId, wholeThreadText); // throws if > 1M chars
// after
const MAX = 1_000_000;
const text = wholeThreadText.length > MAX
  ? "…" + wholeThreadText.slice(-MAX + 1000) // keep the tail, note truncation
  : wholeThreadText;
await enqueueResponse(issueId, text);
Defensive patterns

Strategy: validation

Validate before calling

const MAX = 1_000_000;
if (text.length > MAX) {
  text = text.slice(0, MAX - 20) + "\n[truncated]"; // or split into parts
}

Type guard

null

Try / catch

try { return projectSafeChatPublicationText(input); }
catch (e) {
  if (e instanceof UnsafeChatPublicationError || e.message.includes("exceeds its processing limit")) {
    return projectSafeChatPublicationText(truncate(input, 1_000_000));
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling projectSafeChatPublicationText (directly or via receipt/enqueueResponse/projected/title/body helpers) with input text whose .length exceeds 1,000,000 — e.g. concatenating an entire issue thread or pasting a huge log dump as a chat reply.

Common situations: An automation forwarding giant build logs or file dumps to a chat provider; a runaway agent appending unbounded context into the response text; aggregating many comments into one message body.

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/1486d8d81567d062. Report an issue: GitHub.

Appendix: source

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

      (_match, label: string, separator: string) =>
        `${label}${separator}[REDACTED]`,
    )
    .replace(CONNECTION_STRING_RE, "[REDACTED]");
}

function truncateByCodePoint(input: string, limit: number): string {
  if (input.length <= limit) return input;
  return Array.from(input).slice(0, limit).join("");
}

/**
 * The only text projection allowed to cross from Paperclip into a provider.
 * It strips internal reasoning/tool/log content, redacts credentials, removes
 * dangerous or token-bearing links, and neutralizes provider-wide mentions.
 */
export function projectSafeChatPublicationText(input: string): string {
  if (input.length > MAX_TEXT_INPUT_LENGTH) {
    throw new UnsafeChatPublicationError(
      "External chat text exceeds its processing limit",
    );
  }
  let output = input.replace(/<\|[^|\r\n]{1,80}\|>/g, "");
  for (const pattern of HIDDEN_BLOCKS) output = output.replace(pattern, "");
  output = stripHiddenSections(output);
  // Strip token-bearing query strings before the general credential scanner.
  // That scanner deliberately consumes uncertain unquoted values aggressively;
  // running it first could eat the visible prose following a Markdown URL.
  output = sanitizeUrls(output);
  output = sanitizeCredentialText(output);
  output = output
    .replace(SLACK_BROADCAST_RE, (_match, name: string) => `@\u200b${name}`)
    .replace(PROVIDER_BROADCAST_RE, (_match, name: string) => `@\u200b${name}`)
    .replace(/[ \t]+\n/g, "\n")
    .replace(/\n{3,}/g, "\n\n")
    .trim();

View on GitHub (pinned to 3f1d897a7c)