jackwener/OpenCLI · error · CommandExecutionError

${label} returned an unexpected payload shape; expected an a

Error message

${label} returned an unexpected payload shape; expected an array of result rows.

What it means

requireRows unwraps a browser-command result (peeling off a {session, data} envelope via unwrapBrowserResult) and throws a CommandExecutionError if what remains is not an array of result rows. This indicates the browser step returned something structurally different from what the command's formatter expects — usually page-structure drift or a wrapped/failed payload. It's a shape contract, not a content check (an empty array passes).

Source

Thrown at clis/_shared/search-adapter.js:42

  const raw = value ?? defaultValue;
  const parsed = typeof raw === 'number' ? raw : Number(raw);
  if (!Number.isInteger(parsed) || parsed < 0) {
    throw new ArgumentError(`${label} must be a non-negative integer, got ${JSON.stringify(value)}`);
  }
  return parsed;
}

export function unwrapBrowserResult(value) {
  if (value && typeof value === 'object' && !Array.isArray(value) && 'session' in value && 'data' in value) {
    return value.data;
  }
  return value;
}

export function requireRows(value, label) {
  const rows = unwrapBrowserResult(value);
  if (!Array.isArray(rows)) {
    throw new CommandExecutionError(`${label} returned an unexpected payload shape; expected an array of result rows.`);
  }
  return rows;
}

export function toHttpsUrl(value, baseUrl) {
  const raw = String(value ?? '').trim();
  if (!raw) return '';
  try {
    const url = new URL(raw, baseUrl);
    if (url.protocol !== 'http:' && url.protocol !== 'https:') return '';
    return url.href;
  } catch {
    return '';
  }
}

export function emptySearchResults(site, query) {
  return new EmptyResultError(`${site} search`, `No ${site} results matched "${query}".`);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log/print the raw browser payload before requireRows to inspect the actual shape.
  2. If a {session,data} envelope is expected, confirm unwrapBrowserResult is being applied and that data is the array.
  3. Update the extraction/selector logic to return an array, or update the command for the site's new DOM.
  4. Pin or upgrade the CLI/adapter versions so payload shape and parser agree.

Example fix

// before (payload shape drifted)
const rows = await runBrowserStep('search', () => page.evaluate(js)); // returns {items:[...]}
requireRows(rows, 'search');
// after
const payload = await runBrowserStep('search', () => page.evaluate(js));
requireRows(Array.isArray(payload) ? payload : payload.items, 'search');
Defensive patterns

Strategy: type-guard

Type guard

function isRows(value) {
  const v = value && typeof value === 'object' && !Array.isArray(value) && 'session' in value && 'data' in value
    ? value.data
    : value;
  return Array.isArray(v);
}

Try / catch

try {
  const rows = requireRows(payload, 'search');
} catch (e) {
  if (e instanceof CommandExecutionError && /unexpected payload shape/.test(e.message)) {
    console.error('Payload:', JSON.stringify(payload).slice(0, 500));
  } else throw e;
}

Prevention

When it happens

Trigger: The page.evaluate/browser step resolves to a non-array: an object without being unwrapped, null/undefined, or a {session,data} envelope whose data is not an array.

Common situations: The site changed its markup so the injected extraction script returns an error object instead of rows; an intermediate wrapper/adapter changed its return shape after a version upgrade; a proxy or login wall returned an HTML page parsed into an object.

Related errors


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