惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
博客园 - 聂微东
博客园 - 司徒正美
Hugging Face - Blog
Hugging Face - Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Google DeepMind News
Google DeepMind News
Recent Announcements
Recent Announcements
IT之家
IT之家
J
Java Code Geeks
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
I
InfoQ
爱范儿
爱范儿
Vercel News
Vercel News
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
博客园 - Franky
U
Unit 42
酷 壳 – CoolShell
酷 壳 – CoolShell
腾讯CDC
F
Fortinet All Blogs
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(qa-lab): cap credential broker request timeouts · ope...
steipete · 2026-05-30 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -1,3 +1,4 @@

1+

import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";

12

import { afterEach, describe, expect, it, vi } from "vitest";

23

import {

34

acquireQaCredentialLease,

@@ -320,6 +321,37 @@ describe("credential lease runtime", () => {

320321

expect(fetchUrl(fetchImpl)).toBe("http://127.0.0.1:3210/qa-credentials/v1/acquire");

321322

});

322323
324+

it("caps oversized convex HTTP timeouts before creating abort signals", async () => {

325+

const timeoutController = new AbortController();

326+

const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockReturnValue(timeoutController.signal);

327+

const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(

328+

jsonResponse({

329+

status: "ok",

330+

credentialId: "cred-timeout",

331+

leaseToken: "lease-timeout",

332+

payload: { groupId: "-100123", driverToken: "driver", sutToken: "sut" },

333+

}),

334+

);

335+
336+

await acquireQaCredentialLease({

337+

kind: "telegram",

338+

source: "convex",

339+

role: "maintainer",

340+

env: {

341+

OPENCLAW_QA_CONVEX_SITE_URL: "https://qa-cred.example.convex.site",

342+

OPENCLAW_QA_CONVEX_SECRET_MAINTAINER: "maintainer-secret",

343+

OPENCLAW_QA_CREDENTIAL_HTTP_TIMEOUT_MS: String(Number.MAX_SAFE_INTEGER),

344+

},

345+

fetchImpl,

346+

resolveEnvPayload: () => ({ groupId: "-1", driverToken: "unused", sutToken: "unused" }),

347+

parsePayload: (payload) =>

348+

payload as { groupId: string; driverToken: string; sutToken: string },

349+

});

350+
351+

expect(timeoutSpy).toHaveBeenCalledWith(MAX_TIMER_TIMEOUT_MS);

352+

expect(fetchInit(fetchImpl).signal).toBe(timeoutController.signal);

353+

});

354+
323355

it("rejects unsafe endpoint prefix overrides", async () => {

324356

await expect(

325357

acquireQaCredentialLease({

Original file line numberDiff line numberDiff line change

@@ -1,5 +1,6 @@

11

import { randomUUID } from "node:crypto";

22

import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";

3+

import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";

34

import { z } from "zod";

45

import {

56

isQaCredentialTruthyOptIn,

@@ -258,14 +259,15 @@ async function postConvexBroker(params: {

258259

timeoutMs: number;

259260

url: string;

260261

}): Promise<unknown> {

262+

const timeoutMs = resolveTimerTimeoutMs(params.timeoutMs, DEFAULT_HTTP_TIMEOUT_MS);

261263

const response = await params.fetchImpl(params.url, {

262264

method: "POST",

263265

headers: {

264266

authorization: `Bearer ${params.authToken}`,

265267

"content-type": "application/json",

266268

},

267269

body: JSON.stringify(params.body),

268-

signal: AbortSignal.timeout(params.timeoutMs),

270+

signal: AbortSignal.timeout(timeoutMs),

269271

});

270272
271273

const text = await response.text();

Original file line numberDiff line numberDiff line change

@@ -1,4 +1,5 @@

11

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";

2+

import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";

23

import { afterEach, describe, expect, it, vi } from "vitest";

34

import {

45

LIVE_TRANSPORT_BASELINE_STANDARD_SCENARIO_IDS,

@@ -7,13 +8,20 @@ import {

78

import { testing } from "./telegram-live.runtime.js";

89
910

const fetchWithSsrFGuardMock = vi.hoisted(() =>

10-

vi.fn(async (params: { url: string; init?: RequestInit; signal?: AbortSignal }) => ({

11-

response: await fetch(params.url, {

12-

...params.init,

13-

signal: params.signal,

11+

vi.fn(

12+

async (params: {

13+

url: string;

14+

init?: RequestInit;

15+

signal?: AbortSignal;

16+

timeoutMs?: number;

17+

}) => ({

18+

response: await fetch(params.url, {

19+

...params.init,

20+

signal: params.signal ?? AbortSignal.timeout(params.timeoutMs ?? 0),

21+

}),

22+

release: async () => {},

1423

}),

15-

release: async () => {},

16-

})),

24+

),

1725

);

1826
1927

vi.mock("openclaw/plugin-sdk/ssrf-runtime", async () => {

@@ -904,6 +912,34 @@ describe("telegram live qa runtime", () => {

904912

expect(signal?.aborted).toBe(true);

905913

});

906914
915+

it("caps oversized Telegram API request deadlines", async () => {

916+

const controller = new AbortController();

917+

const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockReturnValue(controller.signal);

918+

vi.stubGlobal(

919+

"fetch",

920+

vi.fn(

921+

async () =>

922+

new Response(JSON.stringify({ ok: true, result: { id: 42 } }), {

923+

status: 200,

924+

headers: {

925+

"content-type": "application/json",

926+

},

927+

}),

928+

),

929+

);

930+
931+

await expect(

932+

testing.callTelegramApi("token", "getMe", undefined, Number.MAX_SAFE_INTEGER),

933+

).resolves.toEqual({

934+

id: 42,

935+

});

936+
937+

expect(timeoutSpy).toHaveBeenCalledWith(MAX_TIMER_TIMEOUT_MS);

938+

expect(fetchWithSsrFGuardMock.mock.calls.at(-1)?.[0]).toMatchObject({

939+

timeoutMs: MAX_TIMER_TIMEOUT_MS,

940+

});

941+

});

942+
907943

it("treats transient Telegram getUpdates network errors as recoverable", () => {

908944

expect(testing.isRecoverableTelegramQaPollError(new TypeError("fetch failed"))).toBe(true);

909945

expect(testing.isRecoverableTelegramQaPollError(new Error("socket hang up"))).toBe(true);

@@ -912,6 +948,7 @@ describe("telegram live qa runtime", () => {

912948

new Error("The operation was aborted due to timeout"),

913949

),

914950

).toBe(true);

951+

expect(testing.isRecoverableTelegramQaPollError(new Error("request timed out"))).toBe(true);

915952

expect(testing.isRecoverableTelegramQaPollError(new Error("AbortError"))).toBe(true);

916953

expect(testing.isRecoverableTelegramQaPollError(new Error("Bad Request: chat not found"))).toBe(

917954

false,

Original file line numberDiff line numberDiff line change

@@ -5,7 +5,10 @@ import path from "node:path";

55

import { promisify } from "node:util";

66

import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";

77

import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";

8-

import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";

8+

import {

9+

parseStrictPositiveInteger,

10+

resolveTimerTimeoutMs,

11+

} from "openclaw/plugin-sdk/number-runtime";

912

import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";

1013

import { isRecord, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";

1114

import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";

@@ -773,6 +776,7 @@ async function callTelegramApi<T>(

773776

body?: Record<string, unknown>,

774777

timeoutMs = 15_000,

775778

): Promise<T> {

779+

const requestTimeoutMs = resolveTimerTimeoutMs(timeoutMs, 15_000);

776780

const { response, release } = await fetchWithSsrFGuard({

777781

url: `https://api.telegram.org/bot${token}/${method}`,

778782

init: {

@@ -782,7 +786,7 @@ async function callTelegramApi<T>(

782786

},

783787

body: JSON.stringify(body ?? {}),

784788

},

785-

signal: AbortSignal.timeout(timeoutMs),

789+

timeoutMs: requestTimeoutMs,

786790

policy: { hostnameAllowlist: ["api.telegram.org"] },

787791

auditContext: "qa-lab-telegram-live",

788792

});

@@ -805,6 +809,7 @@ function isRecoverableTelegramQaPollError(error: unknown): boolean {

805809

message.includes("fetch failed") ||

806810

message.includes("aborted due to timeout") ||

807811

message.includes("operation was aborted") ||

812+

message.includes("request timed out") ||

808813

message.includes("aborterror") ||

809814

message.includes("econnreset") ||

810815

message.includes("etimedout") ||

Original file line numberDiff line numberDiff line change

@@ -1,4 +1,5 @@

1-

import { describe, expect, it, vi } from "vitest";

1+

import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";

2+

import { afterEach, describe, expect, it, vi } from "vitest";

23

import {

34

addQaCredentialSet,

45

diagnoseQaCredentialBroker,

@@ -52,6 +53,10 @@ async function expectQaCredentialAdminError(promise: Promise<unknown>, code: str

5253

}

5354
5455

describe("qa credential admin runtime", () => {

56+

afterEach(() => {

57+

vi.restoreAllMocks();

58+

});

59+
5560

it("adds a credential set through the admin endpoint", async () => {

5661

const fetchImpl = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) =>

5762

jsonResponse({

@@ -151,6 +156,30 @@ describe("qa credential admin runtime", () => {

151156

);

152157

});

153158
159+

it("caps oversized admin HTTP timeouts before creating abort signals", async () => {

160+

const timeoutController = new AbortController();

161+

const timeoutSpy = vi.spyOn(AbortSignal, "timeout").mockReturnValue(timeoutController.signal);

162+

const fetchImpl = vi.fn(async (_input: RequestInfo | URL, _init?: RequestInit) =>

163+

jsonResponse({

164+

status: "ok",

165+

count: 0,

166+

credentials: [],

167+

}),

168+

);

169+
170+

await listQaCredentialSets({

171+

siteUrl: "https://first-schnauzer-821.convex.site",

172+

env: {

173+

OPENCLAW_QA_CONVEX_SECRET_MAINTAINER: "maint-secret",

174+

OPENCLAW_QA_CREDENTIAL_HTTP_TIMEOUT_MS: String(Number.MAX_SAFE_INTEGER),

175+

},

176+

fetchImpl,

177+

});

178+
179+

expect(timeoutSpy).toHaveBeenCalledWith(MAX_TIMER_TIMEOUT_MS);

180+

expect(requireFirstFetchInit(fetchImpl).signal).toBe(timeoutController.signal);

181+

});

182+
154183

it("rejects unsafe endpoint-prefix overrides", async () => {

155184

await expectQaCredentialAdminError(

156185

listQaCredentialSets({

Original file line numberDiff line numberDiff line change

@@ -1,5 +1,6 @@

11

import { randomUUID } from "node:crypto";

22

import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";

3+

import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";

34

import { z } from "zod";

45

import {

56

joinQaCredentialEndpoint,

@@ -371,6 +372,7 @@ async function postJson<T>(params: {

371372

responseSchema: z.ZodType<T>;

372373

url: string;

373374

}) {

375+

const httpTimeoutMs = resolveTimerTimeoutMs(params.httpTimeoutMs, DEFAULT_HTTP_TIMEOUT_MS);

374376

let response: Response;

375377

try {

376378

response = await params.fetchImpl(params.url, {

@@ -380,7 +382,7 @@ async function postJson<T>(params: {

380382

"content-type": "application/json",

381383

},

382384

body: JSON.stringify(params.body),

383-

signal: AbortSignal.timeout(params.httpTimeoutMs),

385+

signal: AbortSignal.timeout(httpTimeoutMs),

384386

});

385387

} catch (error) {

386388

throw new QaCredentialAdminError({