jackwener/OpenCLI · error · Error
[taxonomy=relay_unavailable] site=powerchina command=search
Error message
[taxonomy=relay_unavailable] site=powerchina command=search detached browser context: ${message} What it means
This is a rewritten relay/browser error. When the underlying headless-browser command fails with a 'detached browser context'-style message (page navigation destroying the execution context, browser target closed, or a relay-detached error), searchRowsFromEntries catches it and re-throws a normalized [taxonomy=relay_unavailable] Error tagged with site=powerchina and command=search. The library does this so upstream callers/retry logic can recognize a transient relay unavailability and retry, instead of seeing an opaque Puppeteer/Playwright internal message.
Source
Thrown at clis/powerchina/search.js:202
apiFailure = cleanText(error instanceof Error ? error.message : String(error || ''));
}
if (apiSucceeded && extractedRows.length === 0) {
return [];
}
if (!apiSucceeded) {
try {
extractedRows = await searchRowsFromEntries(page, {
query,
candidateUrls: buildSearchCandidates(query),
allowedHostFragments: ['bid.powerchina.cn', 'powerchina.cn'],
limit,
});
} catch (error) {
const message = cleanText(error instanceof Error ? error.message : String(error || ''));
if (RETRYABLE_SEARCH_ERROR_HINT.test(message)) {
throw new Error(`[taxonomy=relay_unavailable] site=powerchina command=search detached browser context: ${message}`);
}
throw error;
}
}
const rows = filterNavigationRows(
dedupeCandidates(extractedRows).map((item) => ({
title: cleanText(item.title),
url: cleanText(item.url),
date: normalizeDate(cleanText(item.date)),
contextText: cleanText(item.contextText),
})),
);
if (rows.length === 0 && extractedRows.length > 0) {
throw new EmptyResultError('powerchina search', 'extracted only navigation/portal rows, no bid entries matched');
}
View on GitHub (pinned to 49907e53dc)
Solutions
- Retry the search command with backoff — the taxonomy tag marks this error as retryable/transient.
- Check that the browser/relay process is stable (memory, restarts) and not being torn down mid-command.
- Re-run with a slower/fewer-candidate configuration to reduce navigation races on bid.powerchina.cn.
- If persistent, verify network reachability of bid.powerchina.cn and that the relay service hosting the browser is healthy.
Example fix
// before
const rows = await searchRowsFromEntries({ ... });
// after
let rows;
for (let attempt = 0; attempt < 3; attempt++) {
try { rows = await searchRowsFromEntries({ ... }); break; }
catch (error) {
if (String(error.message).includes('relay_unavailable') && attempt < 2) {
await new Promise(r => setTimeout(r, 1000 * 2 ** attempt));
continue;
}
throw error;
}
} Defensive patterns
Strategy: retry
Validate before calling
// preflight: ensure browser/relay is reachable before issuing the command
const healthy = await fetch(relayHealthUrl, { signal: AbortSignal.timeout(5000) }).then(r => r.ok).catch(() => false);
if (!healthy) throw new Error('relay unavailable, aborting search'); Type guard
function isRelayUnavailableError(err) {
return err instanceof Error && /relay_unavailable/.test(err.message);
} Try / catch
try {
rows = await searchPowerchina(query);
} catch (err) {
if (isRelayUnavailableError(err)) {
rows = await withBackoff(() => searchPowerchina(query), { retries: 3 });
} else throw err;
} Prevention
- Always wrap browser-scrape commands in a retry-with-exponential-backoff helper keyed on the [taxonomy=relay_unavailable] tag.
- Monitor relay/browser process health (memory, restarts) before long scraping batches.
- Keep a stable, warmed-up browser session rather than launching a fresh context per request.
- Avoid issuing search and navigation concurrently in the same page to prevent execution-context destruction.
When it happens
Trigger: Calling the powerchina search command when, during searchRowsFromEntries' page.goto/evaluate calls against bid.powerchina.cn, the page navigates or the browser target closes, and error.message matches RETRYABLE_SEARCH_ERROR_HINT (/(detached while handling command|execution context was destroyed|target closed|cannot find context with specified id)/i).
Common situations: SPA re-render/navigation racing the DOM evaluation on the search page; headless browser process crashing or being killed mid-scrape (OOM, supervisor restart); relay/remote-browser session expiring between navigation and evaluation; slow network causing timeouts that close the target.
Related errors
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/5512341f0bebe5b7.
Report an issue: GitHub.