Skip to content

Commit 97d609c

Browse files
committed
fix(thread-ownership): preserve conflict cancellation
1 parent abf347e commit 97d609c

2 files changed

Lines changed: 42 additions & 10 deletions

File tree

extensions/thread-ownership/index.test.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,17 +269,40 @@ describe("thread-ownership plugin", () => {
269269
expect(infoMessage).toContain("cancelled send");
270270
});
271271

272-
it("fails open when the forwarder conflict JSON exceeds the bounded read limit", async () => {
272+
it("cancels when the forwarder conflict JSON exceeds the bounded read limit", async () => {
273273
vi.mocked(globalThis.fetch).mockResolvedValue(
274274
new Response(JSON.stringify({ owner: "x".repeat(70 * 1024) }), { status: 409 }),
275275
);
276276

277277
const result = await sendSlackThreadMessage();
278278

279-
expect(result).toBeUndefined();
280-
const warningMessage = requireFirstLogMessage(api.logger.warn, "ownership check warning log");
281-
expect(warningMessage).toContain("ownership check failed");
279+
expect(result).toEqual({ cancel: true });
280+
const warningMessage = requireFirstLogMessage(
281+
api.logger.warn,
282+
"ownership conflict warning log",
283+
);
284+
expect(warningMessage).toContain("conflict body unreadable");
282285
expect(warningMessage).toContain("JSON response exceeds 65536 bytes");
286+
const infoMessage = requireFirstLogMessage(api.logger.info, "ownership cancel info log");
287+
expect(infoMessage).toContain("cancelled send");
288+
expect(infoMessage).toContain("owned by unknown");
289+
});
290+
291+
it("cancels when the forwarder conflict JSON is malformed", async () => {
292+
vi.mocked(globalThis.fetch).mockResolvedValue(new Response("{", { status: 409 }));
293+
294+
const result = await sendSlackThreadMessage();
295+
296+
expect(result).toEqual({ cancel: true });
297+
const warningMessage = requireFirstLogMessage(
298+
api.logger.warn,
299+
"ownership conflict warning log",
300+
);
301+
expect(warningMessage).toContain("conflict body unreadable");
302+
expect(warningMessage).toContain("malformed JSON response");
303+
const infoMessage = requireFirstLogMessage(api.logger.info, "ownership cancel info log");
304+
expect(infoMessage).toContain("cancelled send");
305+
expect(infoMessage).toContain("owned by unknown");
283306
});
284307

285308
it("fails open on network error", async () => {

extensions/thread-ownership/index.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,21 @@ export default definePluginEntry({
198198
return undefined;
199199
}
200200
if (resp.status === 409) {
201-
const body = await readProviderJsonResponse<{ owner?: unknown }>(
202-
resp,
203-
"thread-ownership forwarder conflict",
204-
{ maxBytes: FORWARDER_CONFLICT_JSON_MAX_BYTES },
205-
);
206-
const owner = typeof body.owner === "string" ? body.owner : undefined;
201+
let owner = "unknown";
202+
try {
203+
const body = await readProviderJsonResponse<{ owner?: unknown }>(
204+
resp,
205+
"thread-ownership forwarder conflict",
206+
{ maxBytes: FORWARDER_CONFLICT_JSON_MAX_BYTES },
207+
);
208+
if (typeof body.owner === "string" && body.owner) {
209+
owner = body.owner;
210+
}
211+
} catch (err) {
212+
api.logger.warn?.(
213+
`thread-ownership: conflict body unreadable (${String(err)}), cancelling send`,
214+
);
215+
}
207216
api.logger.info?.(
208217
`thread-ownership: cancelled send to ${channelId}:${threadTs} — owned by ${owner}`,
209218
);

0 commit comments

Comments
 (0)