jackwener/OpenCLI · error · CommandExecutionError

hltv match-map parser returned an unexpected shape

Error message

hltv match-map parser returned an unexpected shape

What it means

After gotoAndWait loads the map-stats page, readMatchMap runs page.evaluate() to scrape player rows into an array. If the browser-side serializer returns something other than an array (e.g. null/undefined because evaluation failed or the page was replaced), a CommandExecutionError with this message is thrown. It is a defensive postcondition on the parser's return shape.

Source

Thrown at clis/hltv/utils.js:601

          dateTime,
          teamScore: currentTeam === teamOne ? teamOneScore : teamTwoScore,
          opponent,
          opponentScore: currentTeam === teamOne ? teamTwoScore : teamOneScore,
          headshots: kills.paren,
          assists: assists.main,
          flashAssists: assists.paren,
          tradedDeaths: deaths.paren,
          multiKills: numberFrom(textOf(tr, '.st-mks')),
          clutches: numberFrom(textOf(tr, '.st-clutches')),
          roundSwingPct: numberFrom(textOf(tr, '.st-roundSwing')),
        },
        url: playerUrl,
      });
    }
    return rows;
  }, { base: BASE, matchStatsId });

  if (!Array.isArray(rows)) throw new CommandExecutionError('hltv match-map parser returned an unexpected shape');
  if (rows.length === 0) throw new CommandExecutionError('hltv match-map parser found no player rows');

  return assertRequiredFields(rows.map((row) => ({
    matchStatsId: row.matchStatsId,
    playerId: row.playerId,
    playerName: row.playerName,
    team: row.team,
    kills: parseNumber(row.kills),
    deaths: parseNumber(row.deaths),
    adr: parseNumber(row.adr),
    kastPct: parseNumber(row.kastPct),
    rating: parseNumber(row.rating),
    opKd: row.opKd,
    details: row.details,
    url: row.url,
  })), 'hltv match-map', ['matchStatsId', 'playerId', 'playerName', 'team', 'url']);
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run with backoff; if the page was an anti-bot interstitial, retrying after the block clears usually yields a proper array
  2. Verify the URL actually renders the stats section by checking for '.stats-table.totalstats' in the loaded page
  3. Slow down request rate / rotate proxies if HLTV is serving guard pages
  4. Update the library if HLTV changed its page structure so the evaluate no longer completes normally

Example fix

// before
const rows = await readMatchMap(page, url); // throws on interstitial
// after
try {
  const rows = await readMatchMap(page, url);
} catch (err) {
  if (/unexpected shape/.test(err.message)) {
    await sleep(5000);
    return readMatchMap(page, url); // retry once
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const rows = await readMatchMap(page, mapstatsUrl);
} catch (err) {
  if (/unexpected shape/.test(String(err?.message))) {
    await sleep(5000);
    return readMatchMap(page, mapstatsUrl); // retry — page may have been a guard page
  }
  throw err;
}

Prevention

When it happens

Trigger: page.evaluate returning non-array — typically when the page navigation was hijacked (login wall, Cloudflare/DDoS-guard interstitial, error page) or the execution context was destroyed mid-evaluation and the wrapper returned undefined.

Common situations: Scraping behind HLTV's anti-bot interstitial pages; running against a stale or crashed page target; proxy or network middlewares injecting HTML so the evaluate context differs; heavy rate-limiting causing the loaded document to not be a stats page.

Related errors


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