can1357/oh-my-pi · error · SearchProviderError

Google returned its JavaScript challenge instead of rendered

Error message

Google returned its JavaScript challenge instead of rendered search results.

What it means

Thrown by callGoogleHtml when the page loads with a 2xx status but its HTML contains the '/httpservice/retry/enablejs' marker with no <h3> result headings — Google served its JavaScript challenge/consent page instead of rendered results. The headless-browser fallback that should execute Google's JS either did not run, failed, or still got the no-JS page.

Source

Thrown at packages/coding-agent/src/web/search/providers/google.ts:156

			throw new SearchProviderError("google", "Google browser search timed out.", 504);
		}
		const message = error instanceof Error ? error.message : String(error);
		throw new SearchProviderError("google", `Google browser search failed: ${message}`, 503);
	}

	const blocked = blockReason(page);
	if (blocked === "traffic") {
		throw new SearchProviderError(
			"google",
			"Google blocked the browser search with an automated-traffic challenge. Try another web search provider or retry later.",
			429,
		);
	}
	if (page.status < 200 || page.status >= 300) {
		throw new SearchProviderError("google", `Google HTML error (${page.status})`, page.status);
	}
	if (blocked === "javascript") {
		throw new SearchProviderError(
			"google",
			"Google returned its JavaScript challenge instead of rendered search results.",
			429,
		);
	}
	return page.html;
}

/** Execute a Google web search with fetch-first loading and a headless-browser fallback. */
export async function searchGoogle(params: SearchParams): Promise<SearchResponse> {
	const numResults = clampNumResults(params.numSearchResults ?? params.limit, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
	const html = await callGoogleHtml(params, numResults);
	const parsed = parseHtmlResults(html);

	const sources: SearchSource[] = [];
	const seen = new Set<string>();
	for (const result of parsed) {
		if (seen.has(result.url)) continue;

View on GitHub (pinned to 9690622007)

Solutions

  1. Ensure the headless browser runtime is installed so the browser fallback can render the JS challenge.
  2. Retry later — Google serves the challenge inconsistently.
  3. Switch to an API-backed search provider (Jina, Gemini) which is not affected by JS challenges.
  4. Update the package; detection and fallback heuristics are maintained against Google changes.

Example fix

# before: minimal image without browser
FROM node:22-slim
# after: include browser deps for the fallback
FROM node:22-slim
RUN npx playwright install --with-deps chromium
Defensive patterns

Strategy: fallback

Type guard

function isJsChallenge(e: unknown): boolean {
  return e instanceof SearchProviderError && e.provider === 'google'
    && e.statusCode === 429 && e.message.includes('JavaScript challenge');
}

Try / catch

try {
  return await search({ provider: 'google', query });
} catch (err) {
  if (isJsChallenge(err)) return await search({ provider: 'jina', query });
  throw err;
}

Prevention

When it happens

Trigger: fetch-first path received the enablejs page and the headless-browser fallback was unavailable, timed out at the `a h3` ready selector, or itself returned a page matching blockReason === 'javascript'; page.status is 2xx and not a traffic block.

Common situations: Headless browser not installed in the environment (fallback skipped or broken); consent-wall regions (e.g. EU cookie consent variants); Google A/B serving the JS challenge to plain fetch clients.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/920330645b5a6581. Report an issue: GitHub.