jackwener/OpenCLI · error · CommandExecutionError

${context} returned malformed row ${index + 1}.

Error message

${context} returned malformed row ${index + 1}.

What it means

requireNonEmptyRowField validates each scraped row before the CLI returns it. If a row is not a plain object (null, array, primitive) it throws CommandExecutionError '<context> returned malformed row N.' This means the in-page scrape produced an element that violates the row contract, indicating a DOM/scrape mismatch rather than legitimately missing data.

Source

Thrown at clis/discord-app/utils.js:36

function requireObjectEvaluateResult(payload, context) {
    const value = unwrapEvaluateResult(payload);
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        throw new CommandExecutionError(`${context} returned malformed browser output.`);
    }
    return value;
}

function requireArrayEvaluateResult(payload, context) {
    const value = unwrapEvaluateResult(payload);
    if (!Array.isArray(value)) {
        throw new CommandExecutionError(`${context} returned malformed browser output.`);
    }
    return value;
}

function requireNonEmptyRowField(row, field, context, index) {
    if (!row || typeof row !== 'object' || Array.isArray(row)) {
        throw new CommandExecutionError(`${context} returned malformed row ${index + 1}.`);
    }
    const value = String(row[field] || '').trim();
    if (!value) {
        throw new CommandExecutionError(`${context} row ${index + 1} is missing ${field}.`);
    }
    return value;
}

function requireRowsWithFields(rows, fields, context) {
    rows.forEach((row, index) => {
        fields.forEach((field) => requireNonEmptyRowField(row, field, context, index));
    });
    return rows;
}

export function isDiscordSnowflake(value) {
    return CHANNEL_ID_RE.test(String(value || '').trim());
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Filter non-object entries from the scrape script's returned array in clis/discord-app/utils.js before validation
  2. Re-run after the page fully loads so placeholder/skeleton nodes are gone
  3. Check whether a recent Discord UI update broke the row-mapping logic in the relevant build*Script
  4. Pin/work around by scraping fewer rows (lower --count/--limit) if only late-loading rows are malformed

Example fix

// inside the page script, before returning
// before
return nodes.map(n => extract(n));
// after
return nodes.map(n => extract(n)).filter(r => r && typeof r === 'object' && !Array.isArray(r));
Defensive patterns

Strategy: validation

Validate before calling

function validateRows(rows) {
  rows.forEach((r, i) => {
    if (!r || typeof r !== 'object' || Array.isArray(r)) {
      throw new Error(`Row ${i + 1} is not an object — filter non-object entries before returning rows.`);
    }
  });
}

Type guard

function isRow(r) {
  return r !== null && typeof r === 'object' && !Array.isArray(r);
}

Try / catch

try {
  const rows = await discordAppChannels(page);
} catch (err) {
  if (/malformed row \d+/.test(String(err.message))) {
    console.error('Scrape produced a non-object row: re-run after full load or fix the row-mapping script.');
  } else throw err;
}

Prevention

When it happens

Trigger: listDiscordChannels/listDiscordServers/readDiscordMessages/listDiscordThreads returning rows where an entry is null or not an object — e.g. the scrape script pushes undefined for unmatched DOM nodes, or Discord renders placeholder/ghost nodes the script maps to non-objects.

Common situations: Discord UI changes causing the row-mapping script to emit undefined entries; a partially-loaded list where skeleton placeholders are captured; custom or modified scrape scripts returning mixed-shape arrays.

Understand the failure class

Related errors


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