mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix(browser): avoid buffering discarded 429 bodies
This commit is contained in:
@@ -77,6 +77,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Models/Alibaba Cloud Model Studio: wire `MODELSTUDIO_API_KEY` through shared env auth, implicit provider discovery, and shell-env fallback so onboarding works outside the wizard too. (#40634) Thanks @pomelo-nwu.
|
||||
- ACP/sessions_spawn: implicitly stream `mode="run"` ACP spawns to parent only for eligible subagent orchestrator sessions (heartbeat `target: "last"` with a usable session-local route), restoring parent progress relays without thread binding. (#42404) Thanks @davidguttman.
|
||||
- Sessions/reset model recompute: clear stale runtime model, context-token, and system-prompt metadata before session resets recompute the replacement session, so resets pick up current defaults and explicit overrides instead of reusing old runtime model state. (#41173) thanks @PonyX-lab.
|
||||
- Browser/Browserbase 429 handling: surface stable no-retry rate-limit guidance without buffering discarded HTTP 429 response bodies from remote browser services. (#40491) thanks @mvanhorn.
|
||||
|
||||
## 2026.3.8
|
||||
|
||||
|
||||
@@ -139,9 +139,11 @@ describe("fetchBrowserJson loopback auth", () => {
|
||||
});
|
||||
|
||||
it("surfaces 429 from HTTP URL as rate-limit error with no-retry hint", async () => {
|
||||
const text = vi.fn(async () => "max concurrent sessions exceeded");
|
||||
const cancel = vi.fn(async () => {});
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () => new Response("max concurrent sessions exceeded", { status: 429 })),
|
||||
vi.fn(async () => ({ ok: false, status: 429, text, body: { cancel } }) as Response),
|
||||
);
|
||||
|
||||
const thrown = await fetchBrowserJson<{ ok: boolean }>("http://127.0.0.1:18888/").catch(
|
||||
@@ -155,6 +157,8 @@ describe("fetchBrowserJson loopback auth", () => {
|
||||
expect(thrown.message).toContain("Browser service rate limit reached");
|
||||
expect(thrown.message).toContain("Do NOT retry the browser tool");
|
||||
expect(thrown.message).not.toContain("max concurrent sessions exceeded");
|
||||
expect(text).not.toHaveBeenCalled();
|
||||
expect(cancel).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("surfaces 429 from HTTP URL without body detail when empty", async () => {
|
||||
|
||||
@@ -153,6 +153,14 @@ function appendBrowserToolModelHint(message: string): string {
|
||||
return `${message} ${BROWSER_TOOL_MODEL_HINT}`;
|
||||
}
|
||||
|
||||
async function discardResponseBody(res: Response): Promise<void> {
|
||||
try {
|
||||
await res.body?.cancel();
|
||||
} catch {
|
||||
// Best effort only; we're already returning a stable error message.
|
||||
}
|
||||
}
|
||||
|
||||
function enhanceDispatcherPathError(url: string, err: unknown): Error {
|
||||
const msg = normalizeErrorMessage(err);
|
||||
const suffix = `${resolveBrowserFetchOperatorHint(url)} ${BROWSER_TOOL_MODEL_HINT}`;
|
||||
@@ -205,13 +213,14 @@ async function fetchHttpJson<T>(
|
||||
try {
|
||||
const res = await fetch(url, { ...init, signal: ctrl.signal });
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => "");
|
||||
if (isRateLimitStatus(res.status)) {
|
||||
// Do not reflect upstream response text into the error surface (log/agent injection risk)
|
||||
await discardResponseBody(res);
|
||||
throw new BrowserServiceError(
|
||||
`${resolveBrowserRateLimitMessage(url)} ${BROWSER_TOOL_MODEL_HINT}`,
|
||||
);
|
||||
}
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new BrowserServiceError(text || `HTTP ${res.status}`);
|
||||
}
|
||||
return (await res.json()) as T;
|
||||
|
||||
Reference in New Issue
Block a user