fix(sse): lazy-load playwright in claudeTurnstileSolver (#7265) (#7566)

Termux/Android's Node reports process.platform === 'android'.
playwright-core's serverRegistry.js throws 'Unsupported platform:
<platform>' from a top-level IIFE at require time, so merely
importing the playwright package crashed — no browser ever launched.

claudeTurnstileSolver.ts was the only playwright consumer in the
codebase with a static top-level import; every other call site
(browserPool.ts, inAppLoginService.ts) already lazy-loads it. That
static import is unconditionally reachable from the Next.js
instrumentation hook on every boot via open-sse/executors/index.ts,
so any unsupported platform crashed the whole server at startup
regardless of which provider was configured.

Fix: import type { Browser, Page } (erased at compile time) and move
the chromium binding to a lazy await import("playwright") inside
solveTurnstile(), matching the existing pattern.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-17 05:32:17 -03:00
committed by GitHub
parent f3d92aec5c
commit a4c2f183e5
3 changed files with 44 additions and 2 deletions

View File

@@ -0,0 +1 @@
- fix(sse): lazy-load playwright in claudeTurnstileSolver so unsupported platforms (e.g. Termux/Android) don't crash on boot (#7265)

View File

@@ -10,7 +10,7 @@
* 6. Returns fresh cookie for tls-client-node
*/
import { chromium, type Browser, type Page } from "playwright";
import type { Browser, Page } from "playwright";
const CLAUDE_WEB_URL = "https://claude.ai";
const CHALLENGE_TIMEOUT = 60000; // 60s to solve challenge
@@ -80,7 +80,9 @@ export async function solveTurnstile(options?: {
let page: Page | null = null;
try {
// Launch headless browser
// Launch headless browser (lazy import — avoids crashing platforms
// playwright-core doesn't support, e.g. Termux/Android, on module load)
const { chromium } = await import("playwright");
browser = await chromium.launch({ headless });
const context = await browser.newContext({
userAgent:

View File

@@ -0,0 +1,39 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
// Regression guard for #7265: on Termux/Android, `process.platform === "android"`.
// `playwright-core`'s serverRegistry.js throws `Unsupported platform: android` from a
// top-level IIFE at *require time* — merely importing the `playwright` package crashes,
// no browser needs to be launched. `claudeTurnstileSolver.ts` used to `import { chromium }
// from "playwright"` statically, and that module is unconditionally reachable from the
// Next.js instrumentation hook on every boot via open-sse/executors/index.ts, so any
// unsupported platform crashed the whole server at startup regardless of configured provider.
const HERE = dirname(fileURLToPath(import.meta.url));
const SOLVER = join(HERE, "../../open-sse/services/claudeTurnstileSolver.ts");
test("claudeTurnstileSolver.ts does not statically import the playwright runtime", () => {
const src = readFileSync(SOLVER, "utf8");
// Only a type-only import of playwright is allowed at module top level.
assert.doesNotMatch(src, /^import\s*\{\s*chromium[^}]*\}\s*from\s*"playwright"/m);
assert.match(src, /^import type \{ Browser, Page \} from "playwright";/m);
// The real chromium binding must come from a lazy dynamic import inside a function body.
assert.match(src, /const \{ chromium \} = await import\("playwright"\);/);
});
test("importing the real executor chain does not throw on an unsupported process.platform", async () => {
const originalDescriptor = Object.getOwnPropertyDescriptor(process, "platform")!;
Object.defineProperty(process, "platform", { value: "android", configurable: true });
try {
// This is the exact reachability chain from the Next.js instrumentation hook:
// instrumentation-node.ts -> open-sse/index.ts -> executors/index.ts -> claude-web*.ts
// -> claudeTurnstileSolver.ts. Before the fix, this threw
// "Unsupported platform: android" purely from the static playwright import.
await import("../../open-sse/executors/index.ts");
} finally {
Object.defineProperty(process, "platform", originalDescriptor);
}
});