{"record":{"id":"6df265451b5dac76","repo":"jackwener/OpenCLI","slug":"context-could-not-verify-returned-message-targe","errorCode":null,"errorMessage":"${context} could not verify returned message target. Expected channel/thread id ${expectedChannelId}, but the message row did not include channel_id.","messagePattern":"(.+?) could not verify returned message target\\. Expected channel/thread id (.+?), but the message row did not include channel_id\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/discord-app/utils.js","lineNumber":604,"sourceCode":"        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\nexport async function resolveDiscordThreadTarget(page, kwargs = {}) {\n    const urlArg = stringArg(kwargs.url);\n    if (urlArg) {\n        const parsed = parseDiscordChannelUrl(urlArg);","sourceCodeStart":586,"sourceCodeEnd":622,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/discord-app/utils.js#L586-L622","documentation":"During target verification each message row must carry a channel_id so the library can prove the messages came from the requested channel. When a well-formed row has an empty/missing channel_id, the check is inconclusive and CommandExecutionError is thrown with the expected id in the detail line.","triggerScenarios":"A row object in rows lacks channel_id (null, undefined, or empty string) while the expected target has a valid thread_id/channel_id — usually because the scraper omitted the field when attributing the row.","commonSituations":"Discord UI variant (forum/thread view) where the extractor fails to stamp channel_id on rows; custom/old scraper output predating the channel_id field; rows reconstructed from partial page data.","solutions":["Update the message extractor so each row includes channel_id.","Re-run the read after letting the channel fully load (wait for Discord content) so rows are stamped correctly.","If you control the rows, hydrate them: rows.map(r => ({ ...r, channel_id: r.channel_id ?? expectedId })).","Compare with \"opencli discord-app messages -f json\" output to see whether channel_id is being produced at all."],"exampleFix":"// before\nrows.push({ id: 'm1', content: 'hi' }); // no channel_id\n// after\nrows.push({ id: 'm1', content: 'hi', channel_id: target.channel_id });","handlingStrategy":"validation","validationCode":"if (rows.some(r => r && typeof r === 'object' && !Array.isArray(r) && (r.channel_id == null || r.channel_id === ''))) throw new Error('Some rows lack channel_id; scraper output stale');","typeGuard":"function hasChannelId(r) { return r !== null && typeof r === 'object' && !Array.isArray(r) && typeof String(r.channel_id ?? '') === 'string' && String(r.channel_id ?? '').length > 0; }","tryCatchPattern":"try { assertDiscordMessageRowsBelongToTarget(rows, target, ctx); } catch (e) { if (String(e.message).includes('could not verify returned message target')) { // re-harvest after content ready: await waitForDiscordContent(page,'messages');\n} else throw e; }","preventionTips":["Wait for Discord content to finish loading before harvesting rows so channel_id gets stamped.","Update extractors when Discord view variants (forum/thread) change row shape.","Spot-check with \"opencli discord-app messages -f json\" that channel_id is present."],"tags":["discord","data-shape","message-verification","scraper-output"],"backgroundTag":"missing-identifier","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}