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
- Format threadId exactly as github:<owner>/<repo>:<prNumber> or with optional issue:/rc: segments using positive integers
- Verify the adapter passed in implements decodeThreadId (use the pinned GitHub adapter)
- Check that appId, installationId, and messageId are numeric IDs as strings/numbers without decoration
- 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
- Build threadIds from a single formatting helper, never string concatenation
- Only positive integers (no leading-zero/zero IDs) in issue/pr/rc segments
- Pass the pinned adapter that implements decodeThreadId
- Unit-test threadId formatting against the accepted regex
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
- github_attachment_canonical_response_unavailable
- Teams destination is missing its conversation identity
- github_attachment_source_mismatch
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
- "tool" is required and must be a string
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/63d1379ea33cf4d9.
Report an issue: GitHub.