Skip to content

fix(sse): classify missing Chromium as a Z.ai host/config cooldown (#13232) - #13777

Merged
diegosouzapw merged 3 commits into
release/v3.8.51from
fix/13232-zai-web-missing-browser-executable
Sep 16, 2026
Merged

diegosouzapw merged 3 commits into
release/v3.8.51from
fix/13232-zai-web-missing-browser-executable

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #13232

Root cause (short)

The Z.ai web transport (open-sse/executors/zai-web.ts::fetchThroughBrowser) drives a real
headed Chromium browser via Playwright to get past Z.ai's CAPTCHA. When the local Playwright
Chromium binary is missing, chromium.launch() throws browserType.launch: Executable doesn't exist at .... The fetchThroughBrowser catch block had no classification for that failure and
always wrapped it as a plain 502 with no extra headers — a status that trips the
whole-provider circuit breaker (AGENTS.md → "Provider Circuit Breaker": 408/500/502/503/504
all 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 GeminiWebExecutor in issue #3516:
isMissingBrowserExecutable() detects the message shape and the executor returns 503 +
X-Omni-Fallback-Hint: connection_cooldown, which accountFallback (via
serviceSupervisorCooldown() in open-sse/config/errorConfig.ts) treats as a short,
non-exponential connection cooldown and explicitly skips the provider breaker. zai-web.ts never
got that same treatment.

Fix

  • Extracted isMissingBrowserExecutable() out of open-sse/executors/gemini-web.ts into a new
    shared helper open-sse/executors/browserExecutableCheck.ts, re-exported from gemini-web.ts
    for backward compatibility (an existing test imports it from there).
  • open-sse/executors/zai-web.ts::fetchThroughBrowser's catch block now checks
    isMissingBrowserExecutable(rawMessage) before falling back to the generic 502; on a match it
    returns 503 + X-Omni-Fallback-Hint: connection_cooldown with an actionable message ("Z.ai
    requires the Playwright Chromium browser, which is not installed. Run npx playwright install chromium on the host...").
  • open-sse/utils/error.ts::makeExecutorErrorResult gained an optional 5th
    extraResponseHeaders parameter (merged into the Response's headers) so zai-web.ts can set
    the fallback hint without constructing the Response by 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 a
different case — a non-2xx status returned by browserBackedChat() mid-flow, not a thrown
launch error.

Per the plan-file's own scope note, the equivalent gap in claude-web, duckduckgo-web.ts, and
cloudflare-playground.ts was flagged as optional/stretch and is not covered by this PR to
keep the diff scoped to the confirmed zai-web bug.

Regression test

tests/unit/zai-web-missing-browser-executable-13232.test.ts — forces chromium.launch() to
fail with the exact reporter error by pointing PLAYWRIGHT_BROWSERS_PATH at an empty temp
directory, then asserts the classified 503 + hint response.

RED (on unfixed zai-web.ts, captured by temporarily restoring the pre-fix file):

✖ returns a classified 503 + X-Omni-Fallback-Hint: connection_cooldown instead of a bare 502 (contrast: gemini-web.ts isMissingBrowserExecutable, #3516)
  AssertionError [ERR_ASSERTION]: zai-web must classify a missing local Chromium install as a host/config error (503), not a generic retryable 502 that trips the whole-provider circuit breaker.
  502 !== 503
ℹ tests 1
ℹ pass 0
ℹ fail 1

GREEN (fixed code):

✔ returns a classified 503 + X-Omni-Fallback-Hint: connection_cooldown instead of a bare 502 (contrast: gemini-web.ts isMissingBrowserExecutable, #3516) (6729.395077ms)
ℹ tests 1
ℹ pass 1
ℹ fail 0

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, unrelated open-sse/utils/stream.ts frozen-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 the gemini-web.ts extraction 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.ts timed out locally
(spawnSync ... ETIMEDOUT, a hardcoded 60s child-process timeout) on this heavily-loaded
devbox. It spawns an unrelated process-isolated fixture that never touches
fetchThroughBrowser/browserBackedChat — confirmed unrelated to this change and reproduces
the same way without any of this PR's edits applied.

diegosouzapw and others added 3 commits September 15, 2026 15:10
…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
@diegosouzapw
diegosouzapw merged commit 0dbd7f4 into release/v3.8.51 Sep 16, 2026
19 of 21 checks passed
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Z.ai web error

1 participant