jackwener/OpenCLI · error · TimeoutError

TimeoutError

Error message

TimeoutError

What it means

gotoAndWait() wraps page.goto (20s timeout) and page.wait (15s timeout) in a try/catch; if the underlying error message matches /timeout/i it rethrows a TimeoutError labeled with the target page and a 15-second value. It means the HLTV page did not load within the budget or the expected selector never appeared within 15 seconds.

Source

Thrown at clis/hltv/utils.js:845

          rowKillsCol: parsed.left,
          colKillsRow: parsed.right,
        });
      }
    }
    return entries;
  });

  if (!Array.isArray(matrix)) return [];
  return matrix;
}

export async function gotoAndWait(page, url, selector, label) {
  try {
    await page.goto(url.toString(), { waitUntil: 'domcontentloaded', settleMs: 1000, timeout: 20000 });
    await page.wait({ selector, timeout: 15000 });
  } catch (error) {
    if (/timeout/i.test(String(error?.message ?? error))) {
      throw new TimeoutError(label, 15);
    }
    throw new CommandExecutionError(`${label} failed: ${error?.message ?? error}`);
  }
}

export function assertRows(rows, command) {
  if (!Array.isArray(rows)) throw new CommandExecutionError(`${command} parser returned an unexpected shape`);
  if (rows.length === 0) throw new EmptyResultError(command, 'No rows were found in the visible HLTV page');
  return rows;
}

export function assertRequiredFields(rows, command, fields) {
  assertRows(rows, command);
  for (const [index, row] of rows.entries()) {
    for (const field of fields) {
      if (row?.[field] === null || row?.[field] === undefined || row?.[field] === '') {
        throw new CommandExecutionError(`${command} parser returned row ${index + 1} without required ${field}`);
      }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the navigation with exponential backoff — transient slowness and guard pages usually clear
  2. Check general connectivity to hltv.org (curl the URL) and your proxy health
  3. Serialize navigations / reduce concurrency if a single browser page is reused for overlapping waits
  4. Increase the timeout budgets in gotoAndWait if legitimate pages are just slow
  5. If timeouts cluster on specific URLs, verify those pages still exist and render the expected selector

Example fix

// before
const rows = await readMatchMap(page, mapstatsUrl); // TimeoutError: hltv match map page
// after
async function withRetry(fn, attempts = 3) {
  for (let i = 0; ; i++) {
    try { return await fn(); }
    catch (err) {
      if (i + 1 >= attempts || !/timeout/i.test(String(err?.message ?? err))) throw err;
      await new Promise((r) => setTimeout(r, 2000 * 2 ** i));
    }
  }
}
const rows = await withRetry(() => readMatchMap(page, mapstatsUrl));
Defensive patterns

Strategy: retry

Validate before calling

// preflight: confirm the page is reachable before spending the 20s+15s budgets
const res = await fetch(url, { method: 'HEAD' });
if (!res.ok) throw new Error(`page unreachable: ${res.status}`);

Try / catch

try {
  const rows = await readMatchMap(page, mapstatsUrl);
} catch (err) {
  if (err instanceof TimeoutError) {
    await new Promise((r) => setTimeout(r, 2000));
    return readMatchMap(page, mapstatsUrl); // retry with backoff
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling any helper that uses gotoAndWait (e.g. readMatchMap, resolveStatsSeriesUrlFromMap) when HLTV is slow or unreachable, the anti-bot/DDoS-guard page never yields the expected selector, the URL 404s slowly, or the network/proxy is too slow for the 15s selector wait.

Common situations: Scraping under heavy rate limiting (HLTV serves interstitials that never contain '.stats-section.stats-match' or 'a[href*="/stats/matches/"]'); flaky proxies;HLTV outages or maintenance; overly aggressive parallel navigation on a single page handle.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/c554bf4b9dfb88a0. Report an issue: GitHub.