From 492daaf709b10274e73a49575c7a9d7dbfba7b07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Facu=5FVi=C3=B1as?= Date: Wed, 11 Mar 2026 18:00:19 -0300 Subject: [PATCH] fix(reliability): add 15s per-request fetch timeout to adapters Individual HTTP calls to Slack/Discord APIs could hang indefinitely if the network stalls. The overall poll deadline only bounds the loop, not each request. Now each fetch() gets AbortSignal.timeout(15_000). Co-Authored-By: Claude Opus 4.6 --- src/resources/extensions/remote-questions/discord-adapter.ts | 2 ++ src/resources/extensions/remote-questions/slack-adapter.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/remote-questions/discord-adapter.ts b/src/resources/extensions/remote-questions/discord-adapter.ts index e477af65a..544675a1c 100644 --- a/src/resources/extensions/remote-questions/discord-adapter.ts +++ b/src/resources/extensions/remote-questions/discord-adapter.ts @@ -6,6 +6,7 @@ import type { ChannelAdapter, RemotePrompt, RemoteDispatchResult, RemoteAnswer, import { formatForDiscord, parseDiscordResponse } from "./format.js"; const DISCORD_API = "https://discord.com/api/v10"; +const PER_REQUEST_TIMEOUT_MS = 15_000; const NUMBER_EMOJIS = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣"]; export class DiscordAdapter implements ChannelAdapter { @@ -108,6 +109,7 @@ export class DiscordAdapter implements ChannelAdapter { init.body = JSON.stringify(body); } + init.signal = AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS); const response = await fetch(`${DISCORD_API}${path}`, init); if (response.status === 204) return {}; if (!response.ok) { diff --git a/src/resources/extensions/remote-questions/slack-adapter.ts b/src/resources/extensions/remote-questions/slack-adapter.ts index 6bc69b036..42b9fcc07 100644 --- a/src/resources/extensions/remote-questions/slack-adapter.ts +++ b/src/resources/extensions/remote-questions/slack-adapter.ts @@ -6,6 +6,7 @@ import type { ChannelAdapter, RemotePrompt, RemoteDispatchResult, RemoteAnswer, import { formatForSlack, parseSlackReply } from "./format.js"; const SLACK_API = "https://slack.com/api"; +const PER_REQUEST_TIMEOUT_MS = 15_000; export class SlackAdapter implements ChannelAdapter { readonly name = "slack" as const; @@ -72,7 +73,7 @@ export class SlackAdapter implements ChannelAdapter { let response: Response; if (isGet) { const qs = new URLSearchParams(Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)]))).toString(); - response = await fetch(`${url}?${qs}`, { method: "GET", headers: { Authorization: `Bearer ${this.token}` } }); + response = await fetch(`${url}?${qs}`, { method: "GET", headers: { Authorization: `Bearer ${this.token}` }, signal: AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS) }); } else { response = await fetch(url, { method: "POST", @@ -81,6 +82,7 @@ export class SlackAdapter implements ChannelAdapter { "Content-Type": "application/json; charset=utf-8", }, body: JSON.stringify(params), + signal: AbortSignal.timeout(PER_REQUEST_TIMEOUT_MS), }); }