fix(sse): classify missing Chromium as a Z.ai host/config cooldown (#13232) - #13777
Merged
diegosouzapw merged 3 commits intoSep 16, 2026
Merged
Conversation
…13232) The Z.ai web transport drives a real headed Chromium browser (Playwright) to get past Z.ai's CAPTCHA. When the local Chromium binary is missing, chromium.launch() throws "Executable doesn't exist at ...", which zai-web.ts's fetchThroughBrowser catch block wrapped as a plain 502 with no fallback hint — a status that trips the whole-provider circuit breaker as if the upstream itself were failing. gemini-web.ts already classifies this exact failure class for issue #3516 (isMissingBrowserExecutable). Extracted that helper into a shared open-sse/executors/browserExecutableCheck.ts (re-exported from gemini-web.ts for backward compatibility) and applied it to zai-web.ts: a missing browser now returns 503 + X-Omni-Fallback-Hint: connection_cooldown with an actionable remediation message, mirroring the Gemini Web precedent. Regression test: tests/unit/zai-web-missing-browser-executable-13232.test.ts
…2-zai-web-missing-browser-executable
…ecutable (base-red fix #13747)
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…iegosouzapw#13232) (diegosouzapw#13777) Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #13232
Root cause (short)
The Z.ai web transport (
open-sse/executors/zai-web.ts::fetchThroughBrowser) drives a realheaded Chromium browser via Playwright to get past Z.ai's CAPTCHA. When the local Playwright
Chromium binary is missing,
chromium.launch()throwsbrowserType.launch: Executable doesn't exist at .... ThefetchThroughBrowsercatch block had no classification for that failure andalways wrapped it as a plain
502with no extra headers — a status that trips thewhole-provider circuit breaker (
AGENTS.md→ "Provider Circuit Breaker":408/500/502/503/504all trip it) as if the upstream itself were failing, instead of applying a short host/config
cooldown.
This is the exact same failure class already handled for
GeminiWebExecutorin issue #3516:isMissingBrowserExecutable()detects the message shape and the executor returns503+X-Omni-Fallback-Hint: connection_cooldown, whichaccountFallback(viaserviceSupervisorCooldown()inopen-sse/config/errorConfig.ts) treats as a short,non-exponential connection cooldown and explicitly skips the provider breaker.
zai-web.tsnevergot that same treatment.
Fix
isMissingBrowserExecutable()out ofopen-sse/executors/gemini-web.tsinto a newshared helper
open-sse/executors/browserExecutableCheck.ts, re-exported fromgemini-web.tsfor backward compatibility (an existing test imports it from there).
open-sse/executors/zai-web.ts::fetchThroughBrowser's catch block now checksisMissingBrowserExecutable(rawMessage)before falling back to the generic502; on a match itreturns 503 +
X-Omni-Fallback-Hint: connection_cooldownwith an actionable message ("Z.airequires the Playwright Chromium browser, which is not installed. Run
npx playwright install chromiumon the host...").open-sse/utils/error.ts::makeExecutorErrorResultgained an optional 5thextraResponseHeadersparameter (merged into theResponse's headers) sozai-web.tscan setthe fallback hint without constructing the
Responseby hand — every other existing caller(14 executors) is unaffected since the parameter is optional.
This does not touch
describeZaiBrowserFailure()(zai-web.ts:440-449), which handles adifferent case — a non-2xx status returned by
browserBackedChat()mid-flow, not a thrownlaunch error.
Per the plan-file's own scope note, the equivalent gap in
claude-web,duckduckgo-web.ts, andcloudflare-playground.tswas flagged as optional/stretch and is not covered by this PR tokeep the diff scoped to the confirmed zai-web bug.
Regression test
tests/unit/zai-web-missing-browser-executable-13232.test.ts— forceschromium.launch()tofail with the exact reporter error by pointing
PLAYWRIGHT_BROWSERS_PATHat an empty tempdirectory, then asserts the classified
503+ hint response.RED (on unfixed
zai-web.ts, captured by temporarily restoring the pre-fix file):GREEN (fixed code):
Gates run
npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files>→ clean, no new warnings.npm run check:open-sse-typecheck→openSseTypecheckErrors=0, OK.node scripts/check/check-file-size.mjs→ no offenders among touched files (one pre-existing, unrelatedopen-sse/utils/stream.tsfrozen-file offender not touched by this PR).node scripts/check/check-complexity.mjs→ OK, 2824 violations vs baseline 3218 (no new offender in touched files).node scripts/check/check-cognitive-complexity.mjs→ OK, 1276 violations vs baseline 1437 (no new offender in touched files).node scripts/check/check-test-discovery.mjs→ OK, new test file discovered, no new orphans.DATA_DIR=$(mktemp -d) node --import tsx/esm --test --test-force-exit tests/unit/gemini-web-missing-browser-3516.test.ts tests/unit/executor-zai-web.test.ts tests/unit/zai-web-attachment-mime-contract.test.ts tests/unit/zai-web-auth-semantics.test.ts tests/unit/zai-web-silent-empty-repro.test.ts→ 45/45 pass (confirms thegemini-web.tsextraction is behavior-preserving and the existing zai-web suites are unaffected).Existing tests aligned
None needed alignment — no pre-existing assertion encoded the old buggy 502 contract.
Note:
tests/unit/zai-web-stream-error-boundary.test.tstimed out locally(
spawnSync ... ETIMEDOUT, a hardcoded 60s child-process timeout) on this heavily-loadeddevbox. It spawns an unrelated process-isolated fixture that never touches
fetchThroughBrowser/browserBackedChat— confirmed unrelated to this change and reproducesthe same way without any of this PR's edits applied.