{"record":{"id":"ff7368cbd233603b","repo":"paperclipai/paperclip","slug":"chat-provider-pretransport-rejected-invalid-slack-session","errorCode":"CHAT_PROVIDER_PRETRANSPORT_REJECTED","errorMessage":"Invalid Slack session destination or status","messagePattern":"Invalid Slack session destination or status","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/chat-slack-sessions.ts","lineNumber":118,"sourceCode":"  botToken: string;\n  threadId: string;\n  status: SlackSessionStatus;\n  fetch?: typeof globalThis.fetch;\n}): Promise<\"updated\" | \"unavailable\"> {\n  const [, channelId, threadTs, extra] = input.threadId.split(\":\");\n  if (\n    !input.threadId.startsWith(\"slack:\") ||\n    !channelId ||\n    !SLACK_CHANNEL_ID.test(channelId) ||\n    !threadTs ||\n    !SLACK_TIMESTAMP.test(threadTs) ||\n    extra !== undefined ||\n    ![\"processing\", \"active\", \"suspended\", \"closed\"].includes(input.status)\n  ) {\n    throw Object.assign(\n      new Error(\"Invalid Slack session destination or status\"),\n      {\n        code: \"CHAT_PROVIDER_PRETRANSPORT_REJECTED\",\n      },\n    );\n  }\n  let response: Response;\n  try {\n    response = await (input.fetch ?? globalThis.fetch)(\n      \"https://slack.com/api/agents.sessions.setStatus\",\n      {\n        method: \"POST\",\n        headers: {\n          authorization: `Bearer ${input.botToken}`,\n          \"content-type\": \"application/json; charset=utf-8\",\n        },\n        body: JSON.stringify({\n          channel_id: channelId,\n          thread_ts: threadTs,\n          status: input.status,\n        }),","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/chat-slack-sessions.ts#L100-L136","documentation":"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.","triggerScenarios":"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\"].","commonSituations":"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.","solutions":["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","Fix the code that produces or persists the session thread ID so it stores the canonical slack:channelId:threadTs form","Constrain the status to the SlackSessionStatus union type so TypeScript rejects unknown statuses at compile time","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"],"exampleFix":"// before\nawait setSlackSessionStatus({ botToken, threadId: session.threadUrl, status: \"open\" });\n// after\nconst m = session.threadUrl.match(/slack\\.com\\/archives\\/(?<ch>C\\w+)\\/p(?<ts>\\d+)/);\nconst threadId = m ? `slack:${m.groups.ch}:${Number(m.groups.ts.slice(0,10))}.${Number(m.groups.ts.slice(10))}` : session.threadId;\nawait setSlackSessionStatus({ botToken, threadId, status: \"active\" satisfies SlackSessionStatus });","handlingStrategy":"validation","validationCode":"const SLACK_CHANNEL_ID = /^C[A-Z0-9]{8,}$/;\nconst SLACK_TIMESTAMP = /^\\d+\\.\\d+$/;\nfunction validateSlackSession(threadId, status) {\n  const [, ch, ts, extra] = threadId.split(\":\");\n  if (!threadId.startsWith(\"slack:\") || !SLACK_CHANNEL_ID.test(ch) || !SLACK_TIMESTAMP.test(ts) || extra !== undefined)\n    throw new Error(`bad threadId: ${threadId}`);\n  if (![\"processing\",\"active\",\"suspended\",\"closed\"].includes(status)) throw new Error(`bad status: ${status}`);\n}","typeGuard":"const isSlackThreadId = (v: unknown): v is `slack:${string}` =>\n  typeof v === \"string\" && /^slack:C[A-Z0-9]+:\\d+\\.\\d+$/.test(v);","tryCatchPattern":"try {\n  await setSlackSessionStatus({ botToken, threadId, status });\n} catch (e) {\n  if ((e as { code?: string }).code === \"CHAT_PROVIDER_PRETRANSPORT_REJECTED\") {\n    // local bug: never reached Slack; do NOT retry — log threadId shape and fix the producer\n    logger.error({ threadIdShape: String(threadId).split(\":\").length }, \"invalid slack session input\");\n    return \"unavailable\";\n  }\n  throw e;\n}","preventionTips":["Always persist the canonical slack:<channelId>:<threadTs> triple at session creation time","Type the status parameter as the SlackSessionStatus union so bad values fail at compile time","Add a round-trip test: create a session, then update its status with the stored id"],"tags":["slack","validation","chat","pretransport-rejected"],"backgroundTag":"invalid-argument-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}