fix: stop LLM retry loop when browser control service is unavailable (#17673)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 90f47fe132
Co-authored-by: tag-assistant <260167501+tag-assistant@users.noreply.github.com>
Co-authored-by: sebslight <19554889+sebslight@users.noreply.github.com>
Reviewed-by: @sebslight
This commit is contained in:
Tag
2026-02-15 21:53:49 -05:00
committed by GitHub
parent 17a148c8a8
commit 6802b155a8
2 changed files with 15 additions and 5 deletions

View File

@@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai
- Web Fetch/Security: cap downloaded response body size before HTML parsing to prevent memory exhaustion from oversized or deeply nested pages. Thanks @xuemian168.
- Skills/Security: restrict `download` installer `targetDir` to the per-skill tools directory to prevent arbitrary file writes. Thanks @Adam55A-code.
- Agents: return an explicit timeout error reply when an embedded run times out before producing any payloads, preventing silent dropped turns during slow cache-refresh transitions. (#16659) Thanks @liaosvcaf and @vignesh07.
- Browser/Agents: when browser control service is unavailable, return explicit non-retry guidance (instead of "try again") so models do not loop on repeated browser tool calls until timeout. (#17673) Thanks @austenstone.
- Agents/OpenAI: force `store=true` for direct OpenAI Responses/Codex runs to preserve multi-turn server-side conversation state, while leaving proxy/non-OpenAI endpoints unchanged. (#16803) Thanks @mark9232 and @vignesh07.
- Agents/Security: sanitize workspace paths before embedding into LLM prompts (strip Unicode control/format chars) to prevent instruction injection via malicious directory names. Thanks @aether-ai-agent.
- Agents/Context: apply configured model `contextWindow` overrides after provider discovery so `lookupContextTokens()` honors operator config values (including discovery-failure paths). (#17404) Thanks @michaelbship and @vignesh07.

View File

@@ -90,9 +90,16 @@ function withLoopbackBrowserAuth(
}
function enhanceBrowserFetchError(url: string, err: unknown, timeoutMs: number): Error {
const hint = isAbsoluteHttp(url)
? "If this is a sandboxed session, ensure the sandbox browser is running and try again."
: `Start (or restart) the OpenClaw gateway (OpenClaw.app menubar, or \`${formatCliCommand("openclaw gateway")}\`) and try again.`;
const isLocal = !isAbsoluteHttp(url);
// Human-facing hint for logs/diagnostics.
const operatorHint = isLocal
? `Restart the OpenClaw gateway (OpenClaw.app menubar, or \`${formatCliCommand("openclaw gateway")}\`).`
: "If this is a sandboxed session, ensure the sandbox browser is running.";
// Model-facing suffix: explicitly tell the LLM NOT to retry.
// Without this, models see "try again" and enter an infinite tool-call loop.
const modelHint =
"Do NOT retry the browser tool — it will keep failing. " +
"Use an alternative approach or inform the user that the browser is currently unavailable.";
const msg = String(err);
const msgLower = msg.toLowerCase();
const looksLikeTimeout =
@@ -103,10 +110,12 @@ function enhanceBrowserFetchError(url: string, err: unknown, timeoutMs: number):
msgLower.includes("aborterror");
if (looksLikeTimeout) {
return new Error(
`Can't reach the OpenClaw browser control service (timed out after ${timeoutMs}ms). ${hint}`,
`Can't reach the OpenClaw browser control service (timed out after ${timeoutMs}ms). ${operatorHint} ${modelHint}`,
);
}
return new Error(`Can't reach the OpenClaw browser control service. ${hint} (${msg})`);
return new Error(
`Can't reach the OpenClaw browser control service. ${operatorHint} ${modelHint} (${msg})`,
);
}
async function fetchHttpJson<T>(