paperclipai/paperclip · error · UnsafeChatPublicationError
External chat text exceeds its projected processing limit
Error message
External chat text exceeds its projected processing limit
What it means
projectSafeChatPublicationText sanitizes text before it leaves Paperclip for an external chat provider (Slack, Discord, etc.). After stripping hidden reasoning/tool sections, redacting credentials, and neutralizing unsafe links and broadcast mentions, it enforces a projected output cap of MAX_TEXT_OUTPUT_LENGTH (4,000,000 UTF-16 units, chat-publication-projection.ts:12). If the sanitized output still exceeds that cap, it throws UnsafeChatPublicationError so oversized text is never published. The input cap is 1,000,000 units; expansion comes from redaction markers and mention neutralization, so this error fires when the input is large or grows substantially during sanitization.
Solutions
- Truncate the text before calling the projection function so post-sanitization output stays under 4,000,000 units
- Move the bulk content (logs, transcripts) into attachments and publish a short summary text instead
- Pre-strip the hidden/log sections yourself (the same patterns the projector strips) so redaction expansion does not push output over the cap
- Check upstream callers for accidental concatenation of large payloads into a single publication text
Example fix
// before
await publish({ classification: "external", source: "agent_comment", text: hugeLog });
// after
const MAX_SAFE_TEXT = 900_000;
const text = hugeLog.length > MAX_SAFE_TEXT
? hugeLog.slice(0, MAX_SAFE_TEXT) + "\n... (truncated; see attachments)"
: hugeLog;
await publish({ classification: "external", source: "agent_comment", text, attachmentIds: [logAttachmentId] }); Defensive patterns
Strategy: validation
Validate before calling
const MAX_PROJECTED_OUTPUT = 4_000_000;
const MAX_SAFE_INPUT = 900_000; // headroom for redaction expansion
if (typeof text !== "string") throw new TypeError("text must be a string");
if (text.length > MAX_SAFE_INPUT) {
text = text.slice(0, MAX_SAFE_INPUT) + "\n... (truncated)";
} Type guard
function isWithinPublicationLimits(text: unknown): text is string {
return typeof text === "string" && text.length <= 900_000;
} Try / catch
try {
const payload = projectSafeChatPublication({ classification: "external", source, text });
} catch (err) {
if (err instanceof UnsafeChatPublicationError && /projected processing limit/.test(err.message)) {
payload = projectSafeChatPublication({ classification: "external", source, text: truncateForPublication(text) });
} else throw err;
} Prevention
- Cap agent-generated text well below 1,000,000 units before publication
- Send large logs/files as attachments instead of inline text
- Remember redaction markers expand text; leave at least 4x headroom against the 4,000,000 output cap
- Add a test with maximum-size input that includes credentials and mentions (worst-case expansion)
When it happens
Trigger: Calling projectSafeChatPublicationText (directly or via projectSafeChatPublication) with a text string whose sanitized output exceeds 4,000,000 UTF-16 units. Realistic cases: input near the 1,000,000-unit input cap where credential redaction ('[REDACTED]' replacements), broadcast neutralization ('@' plus zero-width space), and hidden-section removal boundaries expand the text ~4x; or an input already above 1,000,000 units would hit the earlier input-limit error instead, so this specific error means the text passed the input check but grew past the output cap during projection.
Common situations: An agent comment dumps a huge log file or build output as its message; a milestone title/body concatenation balloons after redaction markers are inserted; a backfill script pushing large document text through explicit_board_send; a provider adapter passing an entire conversation transcript as publication text.
Understand the failure class
Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.
Related errors
- External chat publications support at most
- CHAT_PROVIDER_PRETRANSPORT_REJECTED
- External chat cards support at most
- External chat progress state is invalid
- paperclip_runner_attachment_staging_count_denied
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/c74e583773b5a3e3.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/chat-publication-projection.ts:266
}
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();
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>();View on GitHub (pinned to 3f1d897a7c)