paperclipai/paperclip · error · DiscordAdapterCompatibilityError
ensureRootThread is unavailable
Error message
ensureRootThread is unavailable
What it means
On a Discord endpoint, ensureDiscordRootThread still requires the adapter to expose an ensureRootThread(channelId, messageId, content) extension. If the wired Discord adapter does not define it, the runtime cannot create the root thread and throws a DiscordAdapterCompatibilityError rather than failing silently.
Source
Thrown at server/src/services/chat-sdk-runtime.ts:2631
async ensureDiscordRootThread(input: {
channelId: string;
content: string;
messageId: string;
}): Promise<void> {
if (this.provider !== "discord") {
throw new DiscordAdapterCompatibilityError(
"ensureDiscordRootThread was called for a non-Discord endpoint",
);
}
const discord = this.adapter as unknown as DiscordAdapterInternals & {
ensureRootThread?: (
channelId: string,
messageId: string,
content: string,
) => Promise<unknown>;
};
if (typeof discord.ensureRootThread !== "function") {
throw new DiscordAdapterCompatibilityError(
"ensureRootThread is unavailable",
);
}
await discord.ensureRootThread.call(
this.adapter,
input.channelId,
input.messageId,
input.content,
);
}
/**
* Reuse the pinned provider parser when a Telegram slash command also
* carries media in its caption. The Chat SDK exposes slash-command fields
* separately from the parsed Message, so without this bridge Paperclip
* would silently discard a captioned document/photo/audio/video.
*/
parseTelegramCommandMessage(raw: unknown): Message | null {View on GitHub (pinned to 01ad858492)
Solutions
- Upgrade the Discord adapter to a version implementing ensureRootThread.
- Feature-detect discord.ensureRootThread before calling and handle the missing capability (skip bootstrap or post to the channel directly).
- Update test doubles to include ensureRootThread.
Example fix
// before
await runtime.ensureDiscordRootThread({ channelId, content, messageId });
// after
const discord = runtime.getProviderAdapter() as { ensureRootThread?: unknown };
if (typeof discord.ensureRootThread === "function") {
await runtime.ensureDiscordRootThread({ channelId, content, messageId });
} else {
await runtime.channel(channelId).post(content);
} Defensive patterns
Strategy: type-guard
Validate before calling
const discord = runtime.getProviderAdapter() as Record<string, unknown>;
if (typeof discord.ensureRootThread !== "function") throw new Error("discord adapter lacks ensureRootThread"); Type guard
function supportsRootThreads(a: unknown): a is { ensureRootThread: (channelId: string, messageId: string, content: string) => Promise<unknown> } {
return typeof a === "object" && a !== null && typeof (a as any).ensureRootThread === "function";
} Try / catch
try {
await runtime.ensureDiscordRootThread({ channelId, content, messageId });
} catch (err) {
if (err instanceof DiscordAdapterCompatibilityError && err.message.includes("ensureRootThread is unavailable")) {
await runtime.channel(channelId).post(content);
} else throw err;
} Prevention
- Pin the Discord adapter to a version implementing ensureRootThread.
- Feature-detect ensureRootThread before root-thread bootstrap and fall back to channel posting.
- Include ensureRootThread in Discord adapter test doubles.
When it happens
Trigger: Calling ensureDiscordRootThread on a Discord runtime whose adapter lacks ensureRootThread — e.g. an older Discord adapter build, a custom adapter, or a test double that only mocks message posting.
Common situations: Discord adapter version pinned before the root-thread support landed; hand-rolled Discord adapters; unit-test stubs missing the extension method.
Related errors
- ensureDiscordRootThread was called for a non-Discord endpoin
- file publication receipt capture is unavailable
- file publication receipt lookup is unavailable
- durable route recorder is unavailable
- durable accepted-activity recorder is unavailable
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/f91af8ca25a0d160.
Report an issue: GitHub.