mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(discord): autoThread race condition when multiple agents mentioned
When multiple agents with autoThread:true are @mentioned in the same message, only the first agent successfully creates a thread. Subsequent agents fail because Discord only allows one thread per message. Previously, the failure was silently caught and the agent would fall back to replying in the parent channel. Now, when thread creation fails, the code re-fetches the message and checks for an existing thread (created by another agent). If found, the agent replies in that thread instead of falling back. Fixes #7508
This commit is contained in:
@@ -2,6 +2,7 @@ import type { Client } from "@buape/carbon";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
|
||||
import {
|
||||
maybeCreateDiscordAutoThread,
|
||||
resolveDiscordAutoThreadContext,
|
||||
resolveDiscordAutoThreadReplyPlan,
|
||||
resolveDiscordReplyDeliveryPlan,
|
||||
@@ -112,6 +113,73 @@ describe("resolveDiscordReplyDeliveryPlan", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("maybeCreateDiscordAutoThread", () => {
|
||||
it("returns existing thread ID when creation fails due to race condition", async () => {
|
||||
// First call succeeds (simulating another agent creating the thread)
|
||||
let callCount = 0;
|
||||
const client = {
|
||||
rest: {
|
||||
post: async () => {
|
||||
callCount++;
|
||||
throw new Error("A thread has already been created on this message");
|
||||
},
|
||||
get: async () => {
|
||||
// Return message with existing thread (simulating race condition resolution)
|
||||
return { thread: { id: "existing-thread" } };
|
||||
},
|
||||
},
|
||||
} as unknown as Client;
|
||||
|
||||
const result = await maybeCreateDiscordAutoThread({
|
||||
client,
|
||||
message: {
|
||||
id: "m1",
|
||||
channelId: "parent",
|
||||
} as unknown as import("./listeners.js").DiscordMessageEvent["message"],
|
||||
isGuildMessage: true,
|
||||
channelConfig: {
|
||||
autoThread: true,
|
||||
} as unknown as import("./allow-list.js").DiscordChannelConfigResolved,
|
||||
threadChannel: null,
|
||||
baseText: "hello",
|
||||
combinedBody: "hello",
|
||||
});
|
||||
|
||||
expect(result).toBe("existing-thread");
|
||||
});
|
||||
|
||||
it("returns undefined when creation fails and no existing thread found", async () => {
|
||||
const client = {
|
||||
rest: {
|
||||
post: async () => {
|
||||
throw new Error("Some other error");
|
||||
},
|
||||
get: async () => {
|
||||
// Message has no thread
|
||||
return { thread: null };
|
||||
},
|
||||
},
|
||||
} as unknown as Client;
|
||||
|
||||
const result = await maybeCreateDiscordAutoThread({
|
||||
client,
|
||||
message: {
|
||||
id: "m1",
|
||||
channelId: "parent",
|
||||
} as unknown as import("./listeners.js").DiscordMessageEvent["message"],
|
||||
isGuildMessage: true,
|
||||
channelConfig: {
|
||||
autoThread: true,
|
||||
} as unknown as import("./allow-list.js").DiscordChannelConfigResolved,
|
||||
threadChannel: null,
|
||||
baseText: "hello",
|
||||
combinedBody: "hello",
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDiscordAutoThreadReplyPlan", () => {
|
||||
it("switches delivery + session context to the created thread", async () => {
|
||||
const client = {
|
||||
|
||||
@@ -358,8 +358,24 @@ export async function maybeCreateDiscordAutoThread(params: {
|
||||
return createdId || undefined;
|
||||
} catch (err) {
|
||||
logVerbose(
|
||||
`discord: autoThread failed for ${params.message.channelId}/${params.message.id}: ${String(err)}`,
|
||||
`discord: autoThread creation failed for ${params.message.channelId}/${params.message.id}: ${String(err)}`,
|
||||
);
|
||||
// Race condition: another agent may have already created a thread on this
|
||||
// message. Re-fetch the message to check for an existing thread.
|
||||
try {
|
||||
const msg = (await params.client.rest.get(
|
||||
Routes.channelMessage(params.message.channelId, params.message.id),
|
||||
)) as { thread?: { id?: string } };
|
||||
const existingThreadId = msg?.thread?.id ? String(msg.thread.id) : "";
|
||||
if (existingThreadId) {
|
||||
logVerbose(
|
||||
`discord: autoThread reusing existing thread ${existingThreadId} on ${params.message.channelId}/${params.message.id}`,
|
||||
);
|
||||
return existingThreadId;
|
||||
}
|
||||
} catch {
|
||||
// If the refetch also fails, fall through to return undefined.
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user