paperclipai/paperclip · error

CHAT_PROVIDER_PRETRANSPORT_REJECTED

CHAT_PROVIDER_PRETRANSPORT_REJECTED

Error message

Invalid GitHub receipt destination

What it means

applyGitHubReceiptReaction validates its inputs before any network call. If the adapter lacks decodeThreadId, appId/installationId/messageId are not numeric, the reaction is not "eyes", the operation is not add/remove, or threadId does not match the strict pattern ^github:owner/repo:(issue:)?N(:rc:N)?$ (chat-github-receipt-reactions.ts:83-89), it throws Error("Invalid GitHub receipt destination") with code CHAT_PROVIDER_PRETRANSPORT_REJECTED. This pre-transport guard guarantees only well-formed GitHub thread destinations ever reach the reaction API.

Source

Thrown at server/src/services/chat-github-receipt-reactions.ts:88

  input: GitHubReceiptMutation,
  assertCurrent: () => Promise<void>,
  fetchImpl: typeof globalThis.fetch = globalThis.fetch,
): Promise<GitHubReceiptIdentity> {
  const adapter = adapterValue as Adapter;
  if (
    !adapter ||
    typeof adapter.decodeThreadId !== "function" ||
    !numericId(appId) ||
    !numericId(installationId) ||
    !numericId(input.messageId) ||
    input.reaction !== "eyes" ||
    !["add", "remove"].includes(input.operation) ||
    !/^github:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+:(?:issue:)?[1-9][0-9]*(?::rc:[1-9][0-9]*)?$/.test(
      input.threadId,
    )
  ) {
    throw Object.assign(new Error("Invalid GitHub receipt destination"), {
      code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED",
    });
  }
  const destination = adapter.decodeThreadId(input.threadId);
  if (
    !/^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(destination.owner) ||
    !/^[A-Za-z0-9_.-]{1,100}$/.test(destination.repo) ||
    [".", ".."].includes(destination.repo) ||
    !numericId(destination.prNumber) ||
    (destination.reviewCommentId !== undefined &&
      !numericId(destination.reviewCommentId))
  ) {
    throw Object.assign(new Error("Invalid GitHub receipt destination"), {
      code: "CHAT_PROVIDER_PRETRANSPORT_REJECTED",
    });
  }
  const client = adapter.octokit;
  if (typeof client?.auth !== "function") throw unavailable("adapter_contract");
  const expected =

View on GitHub (pinned to 01ad858492)

Solutions

  1. Format threadId exactly as github:<owner>/<repo>:<prNumber> or with optional issue:/rc: segments using positive integers
  2. Verify the adapter passed in implements decodeThreadId (use the pinned GitHub adapter)
  3. Check that appId, installationId, and messageId are numeric IDs as strings/numbers without decoration
  4. Log the rejected input and compare it against the accepted regex to find the mismatched segment

Example fix

// before
await applyGitHubReceiptReaction(adapter, appId, installationId, { threadId: "github:org/repo:pr/123", reaction: "eyes", operation: "add", ... });
// after
await applyGitHubReceiptReaction(adapter, appId, installationId, { threadId: "github:org/repo:123", reaction: "eyes", operation: "add", ... });
Defensive patterns

Strategy: validation

Validate before calling

const THREAD_RE = /^github:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+:(?:issue:)?[1-9][0-9]*(?::rc:[1-9][0-9]*)?$/; if (!THREAD_RE.test(threadId) || !numericId(appId) || !numericId(installationId) || !numericId(messageId)) throw new Error("invalid receipt destination");

Type guard

function isGitHubReceiptInput(x) { return typeof x?.threadId === "string" && /^github:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+:(?:issue:)?[1-9][0-9]*(?::rc:[1-9][0-9]*)?$/.test(x.threadId) && (x.reaction === "eyes") && ["add","remove"].includes(x.operation); }

Try / catch

try { await applyGitHubReceiptReaction(adapter, appId, installationId, input, assertCurrent); } catch (e) { if (e?.code === "CHAT_PROVIDER_PRETRANSPORT_REJECTED") { logInvalidDestination(input); return; } throw e; }

Prevention

When it happens

Trigger: Calling applyGitHubReceiptReaction with a threadId like "github:owner/repo:0" (zero not allowed), a missing github: prefix, a malformed review-comment segment, or a non-numeric appId/installationId/messageId; passing a wrong/partial adapter object.

Common situations: Constructing threadIds by string concatenation with unpadded/zero IDs; decoding failures upstream producing altered threadId strings; older adapters without decodeThreadId being passed to this new API; copying threadIds from other providers (slack:, discord:) into the GitHub receipt path.

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@01ad858492 (2026-09-10). Data as JSON: /api/errors/63d1379ea33cf4d9. Report an issue: GitHub.