{"record":{"id":"7fa45c608449f9a9","repo":"jackwener/OpenCLI","slug":"context-returned-malformed-message-rows","errorCode":null,"errorMessage":"${context} returned malformed message rows.","messagePattern":"(.+?) returned malformed message rows\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/discord-app/utils.js","lineNumber":600,"sourceCode":"}\n\nexport async function listDiscordThreads(page, limit) {\n    return requireRowsWithFields(\n        requireArrayEvaluateResult(await page.evaluate(buildListThreadsScript(limit)), 'Discord thread list'),\n        ['Thread', 'guild_id', 'channel_id', 'thread_id', 'url'],\n        'Discord thread list',\n    );\n}\n\nexport function assertDiscordMessageRowsBelongToTarget(rows, target, context = 'Discord read') {\n    if (!target) return rows;\n    const expectedChannelId = String(target.thread_id || target.channel_id || '');\n    if (!expectedChannelId) {\n        throw new CommandExecutionError(`${context} target is missing a stable channel/thread id.`);\n    }\n    for (const row of rows) {\n        if (!row || typeof row !== 'object' || Array.isArray(row)) {\n            throw new CommandExecutionError(`${context} returned malformed message rows.`);\n        }\n        const actualChannelId = row.channel_id == null || row.channel_id === '' ? '' : String(row.channel_id);\n        if (!actualChannelId) {\n            throw new CommandExecutionError(\n                `${context} could not verify returned message target.`,\n                `Expected channel/thread id ${expectedChannelId}, but the message row did not include channel_id.`,\n            );\n        }\n        if (actualChannelId && actualChannelId !== expectedChannelId) {\n            throw new CommandExecutionError(\n                `${context} returned messages from the wrong Discord target.`,\n                `Expected channel/thread id ${expectedChannelId}, saw ${actualChannelId}.`,\n            );\n        }\n    }\n    return rows;\n}\n","sourceCodeStart":582,"sourceCodeEnd":618,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/discord-app/utils.js#L582-L618","documentation":"assertDiscordMessageRowsBelongToTarget iterates every message row and requires each to be a non-null, non-array object. Anything else (null, a string, an array) means the scraper returned structurally broken data, so it throws CommandExecutionError.","triggerScenarios":"A row in the rows array passed to assertDiscordMessageRowsBelongToTarget is null, a primitive, or an Array — e.g. the DOM scraper emitted placeholder/null entries when no messages rendered.","commonSituations":"Reading messages from an empty channel where the scraper returns [null]; a Discord UI update changing selectors so the extractor returns raw strings; bugs in a custom row extractor.","solutions":["Filter out non-object rows before calling: rows.filter(r => r && typeof r === 'object' && !Array.isArray(r)).","Update/patch the message scraper so every emitted row is an object with channel_id and id fields.","Check the Discord app DOM state — re-open the channel and retry if the UI was mid-render.","Report/inspect upstream scraper output with \"opencli discord-app messages -f json\" to see the malformed payload."],"exampleFix":"// before\nassertDiscordMessageRowsBelongToTarget(rows, target); // rows contains nulls\n// after\nconst clean = rows.filter((r) => r && typeof r === 'object' && !Array.isArray(r));\nassertDiscordMessageRowsBelongToTarget(clean, target);","handlingStrategy":"type-guard","validationCode":"const bad = rows.findIndex(r => !r || typeof r !== 'object' || Array.isArray(r));\nif (bad !== -1) throw new Error(`Row ${bad} is not a message object`);","typeGuard":"function isMessageRow(r) { return r !== null && typeof r === 'object' && !Array.isArray(r); }","tryCatchPattern":"try { assertDiscordMessageRowsBelongToTarget(rows, target, ctx); } catch (e) { if (String(e.message).includes('malformed message rows')) { rows = rows.filter(isMessageRow); return assertDiscordMessageRowsBelongToTarget(rows, target, ctx); } throw e; }","preventionTips":["Sanitize scraper output (drop null/primitive/array entries) before verification.","Keep the extractor in sync with Discord DOM changes.","Inspect raw JSON output when reads start failing after UI updates."],"tags":["discord","data-shape","scraper-output","message-verification"],"backgroundTag":"malformed-response-data","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}