{"record":{"id":"99feafbdac236e66","repo":"jackwener/OpenCLI","slug":"discord-channel-navigation-requires-both-guild-id","errorCode":null,"errorMessage":"Discord channel navigation requires both guild_id and channel_id.","messagePattern":"Discord channel navigation requires both guild_id and channel_id\\.","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/discord-app/utils.js","lineNumber":94,"sourceCode":"    const parts = url.pathname.split('/').filter(Boolean);\n    if (parts[0] !== 'channels' || parts.length < 3) return null;\n\n    const guildId = decodeURIComponent(parts[1] || '');\n    const channelId = decodeURIComponent(parts[2] || '');\n    const threadId = parts[3] ? decodeURIComponent(parts[3]) : undefined;\n    if (!guildId || !channelId) return null;\n\n    return {\n        guild_id: guildId,\n        channel_id: channelId,\n        ...(threadId ? { thread_id: threadId } : {}),\n        url: buildDiscordChannelUrl({ guildId, channelId, threadId }),\n    };\n}\n\nexport function buildDiscordChannelUrl({ guildId, channelId, threadId }) {\n    if (!guildId || !channelId) {\n        throw new ArgumentError('Discord channel navigation requires both guild_id and channel_id.');\n    }\n    const base = `${DISCORD_ORIGIN}/channels/${encodeURIComponent(String(guildId))}/${encodeURIComponent(String(channelId))}`;\n    return threadId ? `${base}/${encodeURIComponent(String(threadId))}` : base;\n}\n\nexport function parsePositiveInt(value, fallback, label) {\n    if (value === undefined || value === null || value === '') return fallback;\n    const raw = String(value).trim();\n    if (!/^\\d+$/.test(raw)) {\n        throw new ArgumentError(`--${label} must be a positive integer.`);\n    }\n    const parsed = parseInt(raw, 10);\n    if (!Number.isFinite(parsed) || parsed <= 0) {\n        throw new ArgumentError(`--${label} must be a positive integer.`);\n    }\n    return parsed;\n}\n","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/discord-app/utils.js#L76-L112","documentation":"buildDiscordChannelUrl constructs the discord.com/channels/<guild>/<channel>[/<thread>] URL and throws ArgumentError when guildId or channelId is missing. Discord channel URLs require both identifiers, so any target resolution that ends up without both (via parseDiscordChannelUrl, resolveDiscordChannelTarget or resolveDiscordThreadTarget) fails fast with this error.","triggerScenarios":"Passing a --url that is not a valid Discord channel URL (missing guild or channel segment); supplying --channel without --guild and no resolvable server context; resolveDiscordThreadTarget building a URL from a thread id without a channel id; programmatic calls to buildDiscordChannelUrl with undefined guildId/channelId.","commonSituations":"Passing a discord.com/invite or discord.com/channels/<guild> partial URL; a malformed custom URL missing path segments; forgetting that name-based targeting requires the channel to be found in the current server before a URL can be built; snowflake typos where one id is empty string.","solutions":["Provide a full Discord channel URL of the form https://discord.com/channels/<guild_id>/<channel_id>","When using id-based targeting, pass both --guild and --channel","Use server/channel names instead of raw ids so resolveDiscordChannelTarget can resolve the missing id from the live app","Verify the URL parse output (parseDiscordChannelUrl) contains both snowflakes before calling navigation"],"exampleFix":"// before\nawait discordAppRead(page, { channel: '123456789012345678' });\n// after\nawait discordAppRead(page, { guild: '987654321098765432', channel: '123456789012345678' });","handlingStrategy":"validation","validationCode":"function validateChannelTarget({ url, guild, channel }) {\n  if (url) {\n    const parts = url.replace(/\\/$/, '').split('/');\n    if (!/^discord\\.com$/.test(new URL(url).hostname) || parts.length < 5) {\n      throw new Error('URL must be https://discord.com/channels/<guild_id>/<channel_id>');\n    }\n  } else if (!guild || !channel) {\n    throw new Error('Provide both --guild and --channel, or a full channel URL.');\n  }\n}","typeGuard":"function isSnowflake(v) {\n  return typeof v === 'string' && /^\\d{15,21}$/.test(v);\n}\nfunction hasCompleteTarget(t) {\n  return isSnowflake(t?.guildId) && isSnowflake(t?.channelId);\n}","tryCatchPattern":"try {\n  await discordAppRead(page, { url: channelUrl });\n} catch (err) {\n  if (String(err.message).includes('requires both guild_id and channel_id')) {\n    console.error('Incomplete target: use /channels/<guild>/<channel> URL or pass --guild and --channel together.');\n  } else throw err;\n}","preventionTips":["Always pass full /channels/<guild>/<channel> URLs","Pair --guild with --channel when using ids","Prefer name-based targeting so the resolver fills missing ids","Validate snowflakes (15-21 digits) before invoking the CLI"],"tags":["discord","argument-validation","url"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}