paperclipai/paperclip · error

CHAT_PROVIDER_PRETRANSPORT_REJECTED

CHAT_PROVIDER_PRETRANSPORT_REJECTED

Error message

Invalid Slack receipt destination

What it means

applySlackReceiptReaction performs strict pre-transport validation of the receipt input: operation must be 'add' or 'remove', reaction must be 'eyes', a botToken must be present and free of CR/LF characters, and the destination must be well-formed. Any violation throws before any HTTP call, tagged CHAT_PROVIDER_PRETRANSPORT_REJECTED. The CR/LF check prevents header injection into the Slack API request.

Solutions

  1. Trim/normalize the botToken when loading (strip trailing newlines) and verify it starts with xoxb-
  2. Ensure only operation 'add'|'remove' and reaction 'eyes' are passed; fix the calling code's constants
  3. Verify Slack credentials are configured in the environment before invoking

Example fix

// before
await applySlackReceiptReaction({ operation: op, reaction, botToken: process.env.SLACK_BOT_TOKEN, ... });
// after
const botToken = process.env.SLACK_BOT_TOKEN?.trim();
if (botToken && !/[\r\n]/.test(botToken) && (op === 'add' || op === 'remove') && reaction === 'eyes') {
  await applySlackReceiptReaction({ operation: op, reaction, botToken, ... });
}
Defensive patterns

Strategy: validation

Validate before calling

const ok = (op === 'add' || op === 'remove') && reaction === 'eyes' && typeof botToken === 'string' && botToken.length > 0 && ![\r\n].some(c => botToken.includes(c));

Type guard

function isValidSlackReceiptInput(i: unknown): i is SlackReceiptInput { const x = i as SlackReceiptInput; return ['add','remove'].includes(x.operation) && x.reaction === 'eyes' && typeof x.botToken === 'string' && x.botToken.length > 0 && ![\r\n].some(c => x.botToken.includes(c)); }

Try / catch

try { await applySlackReceiptReaction(input); } catch (err) { if ((err as { code?: string }).code === 'CHAT_PROVIDER_PRETRANSPORT_REJECTED') { fixInputAndRequeue(input); return; } throw err; }

Prevention

When it happens

Trigger: Calling with operation other than add/remove; a reaction name other than 'eyes'; a missing/empty botToken; a botToken containing \r or \n (e.g. pasted multiline secret or secret containing a trailing newline from env/file read).

Common situations: Slack bot token loaded from a file/env with a trailing newline; wiring the wrong reaction name constant; misrouted handler passing a Teams-style operation value; missing Slack credentials in an environment.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/b6cc8298b0968bf4. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/chat-slack-receipts.ts:70

export async function applySlackReceiptReaction(
  input: SlackReceiptMutation & { botToken: string },
  fetchImpl: typeof globalThis.fetch = globalThis.fetch,
): Promise<void> {
  const parts = input.threadId.split(":");
  if (
    parts[0] !== "slack" ||
    !/^[CDG][A-Z0-9]+$/.test(parts[1] ?? "") ||
    parts.length < 2 ||
    parts.length > 3 ||
    (parts[2] !== undefined && !/^\d{1,12}\.\d{1,6}$/.test(parts[2])) ||
    !/^\d{1,12}\.\d{1,6}$/.test(input.messageId) ||
    !["add", "remove"].includes(input.operation) ||
    input.reaction !== "eyes" ||
    !input.botToken ||
    /[\r\n]/.test(input.botToken)
  ) {
    throw Object.assign(new Error("Invalid Slack receipt destination"), {
      code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED",
    });
  }
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), SLACK_RECEIPT_TIMEOUT_MS);
  let response: Response;
  let body: Record<string, unknown> | null = null;
  try {
    response = await fetchImpl(
      `https://slack.com/api/reactions.${input.operation}`,
      {
        method: "POST",
        redirect: "error",
        signal: controller.signal,
        headers: {
          authorization: `Bearer ${input.botToken}`,
          "content-type": "application/json; charset=utf-8",
        },
        body: JSON.stringify({

View on GitHub (pinned to 01ad858492)