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

  1. Retry the search command with backoff — the taxonomy tag marks this error as retryable/transient.
  2. Check that the browser/relay process is stable (memory, restarts) and not being torn down mid-command.
  3. Re-run with a slower/fewer-candidate configuration to reduce navigation races on bid.powerchina.cn.
  4. 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

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.