paperclipai/paperclip · error
Telegram durable draft transport is unavailable
Error message
Telegram durable draft transport is unavailable
What it means
streamTelegramDraft only forwards a text stream to the Telegram adapter when three invariants hold: the runtime provider is 'telegram', the thread resolves to a private-chat draft destination, and the adapter declares paperclipDraftStopVersion === 1 (the durable stop protocol). If any check fails, the runtime refuses to start a draft stream it cannot durably control, so it throws this plain Error instead of silently degrading to a non-durable transport.
Source
Thrown at server/src/services/chat-sdk-runtime.ts:2467
async streamTelegramDraft(
threadId: string,
textStream: AsyncIterable<string>,
control: TelegramDraftControl,
): Promise<{ id: string } | TelegramDraftStopped> {
const adapter = this.adapter as unknown as {
paperclipDraftStopVersion?: number;
stream(
threadId: string,
stream: AsyncIterable<string>,
options: unknown,
): Promise<{ id: string } | TelegramDraftStopped>;
};
if (
this.provider !== "telegram" ||
!telegramPrivateDraftDestination(threadId) ||
adapter.paperclipDraftStopVersion !== 1
) {
throw new Error("Telegram durable draft transport is unavailable");
}
return adapter.stream(threadId, textStream, {
paperclipDraftControl: control,
});
}
/**
* Post one Slack file-only publication while durably recording the accepted
* upload IDs before the adapter performs its eventually-consistent share
* lookup. This specialized receipt scope is deliberately unavailable for
* cards, edits, and ordinary text sends; the send still uses Thread.post.
*/
async postSlackFilePublication(
threadId: string,
message: Parameters<Thread["post"]>[0],
onUploadAccepted: (
receipt: SlackFileUploadAcceptedReceipt,
) => Promise<void>,View on GitHub (pinned to 01ad858492)
Solutions
- Verify the endpoint runtime is bound to the telegram provider before calling streamTelegramDraft (check this.provider / endpoint config).
- Confirm the threadId denotes a private chat by running telegramPrivateDraftDestination(threadId) first; use the ordinary Thread.post path for group/channel threads.
- Upgrade or re-wire the Telegram adapter so it exposes paperclipDraftStopVersion === 1, or feature-detect it and fall back to a plain post.
- Wrap the call in try/catch and fall back to non-durable sending when the transport is unavailable.
Example fix
// before
await runtime.streamTelegramDraft(threadId, textStream, control);
// after
if (
runtime.provider === "telegram" &&
telegramPrivateDraftDestination(threadId) &&
(runtime.getProviderAdapter() as { paperclipDraftStopVersion?: number })
.paperclipDraftStopVersion === 1
) {
await runtime.streamTelegramDraft(threadId, textStream, control);
} else {
await runtime.thread(threadId).post(text);
} Defensive patterns
Strategy: fallback
Validate before calling
function canStreamTelegramDraft(runtime: unknown, threadId: string): boolean {
const r = runtime as { provider?: string; getProviderAdapter?: () => unknown };
const adapter = r.getProviderAdapter?.() as { paperclipDraftStopVersion?: number } | undefined;
return r.provider === "telegram" && telegramPrivateDraftDestination(threadId) && adapter?.paperclipDraftStopVersion === 1;
} Type guard
function isDraftCapableTelegramAdapter(a: unknown): a is { paperclipDraftStopVersion: 1; stream: Function } {
return typeof a === "object" && a !== null && (a as any).paperclipDraftStopVersion === 1 && typeof (a as any).stream === "function";
} Try / catch
try {
await runtime.streamTelegramDraft(threadId, textStream, control);
} catch (err) {
if (err instanceof Error && err.message.includes("durable draft transport is unavailable")) {
await runtime.thread(threadId).post(text);
} else throw err;
} Prevention
- Always gate Telegram draft streaming behind a provider + telegramPrivateDraftDestination check.
- Feature-detect paperclipDraftStopVersion before using draft streaming.
- Keep the Telegram adapter pinned to a version supporting draft-stop v1 in production deployments.
When it happens
Trigger: Calling streamTelegramDraft(threadId, textStream, control) when: (1) the runtime was constructed for a provider other than 'telegram'; (2) telegramPrivateDraftDestination(threadId) returns falsy, i.e. the thread id is not a private-chat draft-capable destination (e.g. a group/channel thread); or (3) the wired Telegram adapter does not export paperclipDraftStopVersion === 1 (stale or third-party adapter build).
Common situations: A deployment where the chat endpoint config was switched to a different provider but old code paths still call the Telegram-only streaming API; a Telegram group/channel thread id passed in instead of a private chat; running an older Telegram adapter (or stubbed adapter in tests) that predates the draft-stop v1 contract.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- file publication receipt capture was called for a non-Slack
- file publication receipt lookup was called for a non-Slack e
- ensureDiscordRootThread was called for a non-Discord endpoin
- decodeThreadId is unavailable
- openDM is unavailable
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/2ab16710c610b932.
Report an issue: GitHub.