paperclipai/paperclip · error

CHAT_PROVIDER_PRETRANSPORT_REJECTED

CHAT_PROVIDER_PRETRANSPORT_REJECTED

Error message

Invalid Slack session destination or status

What it means

setSlackSessionStatus performs pre-transport validation of the Slack session destination and status before calling the Slack agents.sessions.setStatus API. This error is thrown when the threadId is not a well-formed "slack:<channelId>:<threadTs>" value (channel ID must match SLACK_CHANNEL_ID, thread timestamp must match SLACK_TIMESTAMP, no extra segments) or the status is not one of processing/active/suspended/closed. It carries code CHAT_PROVIDER_PRETRANSPORT_REJECTED, meaning the request was rejected locally and never sent to Slack.

Solutions

  1. Log/inspect the exact input.threadId and input.status values at the call site and compare against the required format slack:<C1234567890>:<1234567890.123456> with exactly three segments
  2. Fix the code that produces or persists the session thread ID so it stores the canonical slack:channelId:threadTs form
  3. Constrain the status to the SlackSessionStatus union type so TypeScript rejects unknown statuses at compile time
  4. If this error surfaces at runtime, treat it as a local bug: do not retry (the request never reached Slack) and mark the session status update as failed per the idempotency contract

Example fix

// before
await setSlackSessionStatus({ botToken, threadId: session.threadUrl, status: "open" });
// after
const m = session.threadUrl.match(/slack\.com\/archives\/(?<ch>C\w+)\/p(?<ts>\d+)/);
const threadId = m ? `slack:${m.groups.ch}:${Number(m.groups.ts.slice(0,10))}.${Number(m.groups.ts.slice(10))}` : session.threadId;
await setSlackSessionStatus({ botToken, threadId, status: "active" satisfies SlackSessionStatus });
Defensive patterns

Strategy: validation

Validate before calling

const SLACK_CHANNEL_ID = /^C[A-Z0-9]{8,}$/;
const SLACK_TIMESTAMP = /^\d+\.\d+$/;
function validateSlackSession(threadId, status) {
  const [, ch, ts, extra] = threadId.split(":");
  if (!threadId.startsWith("slack:") || !SLACK_CHANNEL_ID.test(ch) || !SLACK_TIMESTAMP.test(ts) || extra !== undefined)
    throw new Error(`bad threadId: ${threadId}`);
  if (!["processing","active","suspended","closed"].includes(status)) throw new Error(`bad status: ${status}`);
}

Type guard

const isSlackThreadId = (v: unknown): v is `slack:${string}` =>
  typeof v === "string" && /^slack:C[A-Z0-9]+:\d+\.\d+$/.test(v);

Try / catch

try {
  await setSlackSessionStatus({ botToken, threadId, status });
} catch (e) {
  if ((e as { code?: string }).code === "CHAT_PROVIDER_PRETRANSPORT_REJECTED") {
    // local bug: never reached Slack; do NOT retry — log threadId shape and fix the producer
    logger.error({ threadIdShape: String(threadId).split(":").length }, "invalid slack session input");
    return "unavailable";
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling setSlackSessionStatus with input.threadId that does not start with "slack:", has an empty or malformed channel ID, an empty or malformed thread timestamp, more than three colon-separated segments, or input.status outside ["processing","active","suspended","closed"].

Common situations: A thread ID was stored in the wrong format (e.g. a bare channel ID or a Slack permalink URL instead of the slack:channel:ts triple); a Discord/Teams thread identifier was passed to the Slack helper; a new status string like "idle" was introduced by a caller without updating this helper; a colon-containing value corrupted the split-based parsing.

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


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

Appendix: source

Thrown at server/src/services/chat-slack-sessions.ts:118

  botToken: string;
  threadId: string;
  status: SlackSessionStatus;
  fetch?: typeof globalThis.fetch;
}): Promise<"updated" | "unavailable"> {
  const [, channelId, threadTs, extra] = input.threadId.split(":");
  if (
    !input.threadId.startsWith("slack:") ||
    !channelId ||
    !SLACK_CHANNEL_ID.test(channelId) ||
    !threadTs ||
    !SLACK_TIMESTAMP.test(threadTs) ||
    extra !== undefined ||
    !["processing", "active", "suspended", "closed"].includes(input.status)
  ) {
    throw Object.assign(
      new Error("Invalid Slack session destination or status"),
      {
        code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED",
      },
    );
  }
  let response: Response;
  try {
    response = await (input.fetch ?? globalThis.fetch)(
      "https://slack.com/api/agents.sessions.setStatus",
      {
        method: "POST",
        headers: {
          authorization: `Bearer ${input.botToken}`,
          "content-type": "application/json; charset=utf-8",
        },
        body: JSON.stringify({
          channel_id: channelId,
          thread_ts: threadTs,
          status: input.status,
        }),

View on GitHub (pinned to 3f1d897a7c)