diff --git a/server/src/services/chat-publication-errors.test.ts b/server/src/services/chat-publication-errors.test.ts index 0635f69c9fc..5bb72024f3f 100644 --- a/server/src/services/chat-publication-errors.test.ts +++ b/server/src/services/chat-publication-errors.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import { forbidden, HttpError, unauthorized } from "../errors.js"; import { classifyChatPublicationError } from "./chat-publication-errors.js"; function providerError( @@ -155,6 +156,27 @@ describe("chat publication error classification", () => { ).toMatchObject({ kind: "endpoint_attention" }); }); + it("keeps Paperclip's own authorization failures scoped to the publication", () => { + expect( + classifyChatPublicationError( + forbidden("GitHub failure has no request binding"), + 1, + ), + ).toMatchObject({ kind: "failed" }); + expect(classifyChatPublicationError(unauthorized(), 1)).toMatchObject({ + kind: "failed", + }); + expect( + classifyChatPublicationError( + new HttpError(403, "GitHub rejected this operation", { + code: "github_bot_operation_failed", + providerStatus: 403, + }), + 1, + ), + ).toMatchObject({ kind: "endpoint_attention" }); + }); + it("does not call explicit GitHub 4xx rejections ambiguous delivery", () => { for (const status of [400, 409, 422]) { expect( diff --git a/server/src/services/chat-publication-errors.ts b/server/src/services/chat-publication-errors.ts index 3d020bbc49c..c9666264063 100644 --- a/server/src/services/chat-publication-errors.ts +++ b/server/src/services/chat-publication-errors.ts @@ -1,3 +1,5 @@ +import { HttpError } from "../errors.js"; + export type ChatPublicationErrorDisposition = | { kind: "retry"; @@ -317,6 +319,15 @@ export function classifyChatPublicationError( return { kind: "resource_unavailable", reason }; } + if ( + error instanceof HttpError && + (error.status === 401 || error.status === 403) && + typeof (error.details as { providerStatus?: unknown } | undefined) + ?.providerStatus !== "number" + ) { + return { kind: "failed", reason }; + } + if ( names.includes("AuthenticationError") || names.includes("PermissionError") ||