{"record":{"id":"8d713844803e7653","repo":"jackwener/OpenCLI","slug":"label-returned-a-malformed-row","errorCode":null,"errorMessage":"${label} returned a malformed row","messagePattern":"(.+?) returned a malformed row","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/grok/export-utils.js","lineNumber":60,"sourceCode":"    }\n    const host = parsed.hostname.toLowerCase();\n    const match = parsed.pathname.match(/^\\/c\\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\\/?$/i);\n    if (parsed.protocol !== 'https:' || (host !== 'grok.com' && !host.endsWith('.grok.com')) || !match) {\n        throw makeError(`invalid url for conversation ${id}`);\n    }\n    if (match[1].toLowerCase() !== id) {\n        throw makeError(`url id mismatch for conversation ${id}`);\n    }\n    return `https://grok.com/c/${id}`;\n}\n\nexport function normalizeConversationRows(rows, label) {\n    if (!Array.isArray(rows)) {\n        throw new CommandExecutionError(`${label} returned malformed rows`, 'Expected rows to be an array.');\n    }\n    return rows.map((row, index) => {\n        if (!row || typeof row !== 'object' || Array.isArray(row)) {\n            throw new CommandExecutionError(`${label} returned a malformed row`, `Row ${index + 1} is not an object.`);\n        }\n        const id = String(row.id || '').trim().toLowerCase();\n        if (!GROK_CONVERSATION_ID_RE.test(id)) {\n            throw new CommandExecutionError(`${label} returned a malformed row`, `Row ${index + 1} is missing a valid Grok conversation id.`);\n        }\n        return {\n            id,\n            title: row.title == null || row.title === '' ? '' : String(row.title),\n            date: row.date == null || row.date === '' ? '' : String(row.date),\n            url: normalizeGrokUrl(row.url, id, (reason) => new CommandExecutionError(`${label} returned a malformed row`, reason)),\n        };\n    });\n}\n\nexport function normalizeManifestRows(rows) {\n    if (!Array.isArray(rows)) {\n        throw new ArgumentError('manifestPath', 'must point to a JSON array exported by grok/export');\n    }","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/grok/export-utils.js#L42-L78","documentation":"normalizeConversationRows validates rows scraped from the Grok conversation list before export. This error is thrown when a row is present in the array but is not a plain object (null, undefined, a primitive, or an Array). The library refuses to guess or coerce such a row, since it cannot read row.id/title/date/url from it.","triggerScenarios":"Calling normalizeConversationRows(rows, label) where rows is an array containing at least one element that is null, undefined, a primitive (string/number/boolean), or an array; the error message includes the 1-based row index in `Row ${index + 1} is not an object.`","commonSituations":"A page-eval extraction script returned null placeholders for conversations that were deleted mid-scrape; a JSON scrape file was hand-edited or truncated; a version change in Grok's DOM scraper produced different shapes; someone passed a top-level JSON object keyed by id instead of an array of rows.","solutions":["Find the offending index (reported as `Row N is not an object.` in the error detail) and inspect the source array at rows[N-1].","Fix the upstream scraper/evaluate payload so every element is an object with id, title, date, url fields before calling normalizeConversationRows.","Filter out non-object entries yourself first: rows = rows.filter(r => r && typeof r === 'object' && !Array.isArray(r)).","If you only have a manifest JSON file, verify it is an array of objects via `node -e \"const d=require('./m.json'); d.forEach((r,i)=>{if(!r||typeof r!=='object'||Array.isArray(r)) throw i+1})\"`."],"exampleFix":"// before\nconst rows = await page.evaluate(() => scrapedRows); // scrapedRows may contain nulls\nconst valid = normalizeConversationRows(rows, 'grok/list');\n\n// after\nconst rows = (await page.evaluate(() => scrapedRows)) ?? [];\nconst cleaned = rows.filter((r) => r && typeof r === 'object' && !Array.isArray(r));\nconst valid = normalizeConversationRows(cleaned, 'grok/list');","handlingStrategy":"validation","validationCode":"function isRowArray(rows) {\n  return Array.isArray(rows) && rows.every((r) => r && typeof r === 'object' && !Array.isArray(r));\n}\nif (!isRowArray(rows)) throw new Error('conversation rows must be an array of objects');","typeGuard":"function isConversationRow(row) {\n  return typeof row === 'object' && row !== null && !Array.isArray(row);\n}","tryCatchPattern":"import { CommandExecutionError } from '@jackwener/opencli/errors';\ntry {\n  const rows = normalizeConversationRows(raw, 'grok/list');\n} catch (err) {\n  if (err instanceof CommandExecutionError) {\n    // err.detail tells you which row index failed\n    console.error(`Grok export rows malformed: ${err.message} — ${err.detail}`);\n  } else throw err;\n}","preventionTips":["Filter out null/primitive entries from page-eval results before normalizing.","Validate the scraper payload shape in a smoke test after any Grok DOM change.","Never hand-edit exported JSON; always round-trip through grok/export."],"tags":["validation","input-validation","schema-mismatch","grok-export"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}