{"record":{"id":"73a65bab54524786","repo":"jackwener/OpenCLI","slug":"context-target-is-missing-a-stable-channel-thre","errorCode":null,"errorMessage":"${context} target is missing a stable channel/thread id.","messagePattern":"(.+?) target is missing a stable channel/thread id\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/discord-app/utils.js","lineNumber":596,"sourceCode":"}\n\nexport async function readDiscordMessages(page, count) {\n    return requireArrayEvaluateResult(await page.evaluate(buildReadMessagesScript(count)), 'Discord message list');\n}\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        }","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/discord-app/utils.js#L578-L614","documentation":"assertDiscordMessageRowsBelongToTarget verifies that fetched message rows came from the intended channel. It first requires the target object itself to carry a stable identifier (thread_id or channel_id); if both are empty, it throws CommandExecutionError because verification is impossible.","triggerScenarios":"Calling assertDiscordMessageRowsBelongToTarget with a target object whose thread_id and channel_id are both missing/empty — typically a target produced without full resolution (e.g. resolveDiscordChannelTarget returned null because it was not required).","commonSituations":"Passing a hand-built { } or partially populated target; using a null-returning optional resolution result downstream; code that builds target from user args without running the resolver.","solutions":["Resolve the target first with resolveDiscordChannelTarget (or thread target) so channel_id/thread_id is populated.","Pass options.required: true to the resolver so it throws early instead of returning null.","Construct the target from the numeric ids shown by \"opencli discord-app channels -f json\".","Skip the assertion only if you truly have no target (the function returns rows unchanged when target is falsy)."],"exampleFix":"// before\nconst target = await resolveDiscordChannelTarget(page, kwargs); // may return null/partial\nassertDiscordMessageRowsBelongToTarget(rows, target); // throws: no stable id\n// after\nconst target = await resolveDiscordChannelTarget(page, kwargs, { required: true });\nassertDiscordMessageRowsBelongToTarget(rows, target);","handlingStrategy":"type-guard","validationCode":"if (!target || !(String(target.thread_id || '') || String(target.channel_id || ''))) throw new Error('Target lacks thread_id/channel_id; resolve it first');","typeGuard":"function hasStableTargetId(t) { return Boolean(t) && Boolean(String(t.thread_id || t.channel_id || '').trim()); }","tryCatchPattern":"try { assertDiscordMessageRowsBelongToTarget(rows, target, ctx); } catch (e) { if (String(e.message).includes('missing a stable channel/thread id')) { // re-resolve target with required:true before proceeding\n} else throw e; }","preventionTips":["Always produce targets via resolveDiscordChannelTarget/resolveDiscordThreadTarget with { required: true }.","Never hand-build target objects from raw user args.","Persist the full resolved target (with ids), not just the label."],"tags":["discord","cli","invariant-violation","message-verification"],"backgroundTag":"missing-identifier","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}