jackwener/OpenCLI · error · ArgumentError

--url must be a Discord channel URL like https://discord.com

Error message

--url must be a Discord channel URL like https://discord.com/channels/<guild_id>/<channel_id>.

What it means

resolveDiscordChannelTarget validates the --url argument by running it through parseDiscordChannelUrl. It throws ArgumentError when the URL cannot be parsed at all or when the URL points at a thread (contains a thread_id), since this resolver only accepts plain channel URLs.

Source

Thrown at clis/discord-app/utils.js:464

}

function rowMatchesChannel(row, channel) {
    if (!channel) return false;
    if (String(row.channel_id || '') === channel) return true;
    return normalizeDiscordName(row.Channel) === normalizeDiscordName(channel);
}

function rowMatchesGuild(row, guild) {
    if (!guild) return true;
    return String(row.guild_id || '') === guild || normalizeDiscordName(row.Server) === normalizeDiscordName(guild);
}

export async function resolveDiscordChannelTarget(page, kwargs = {}, options = {}) {
    const urlArg = stringArg(kwargs.url);
    if (urlArg) {
        const parsed = parseDiscordChannelUrl(urlArg);
        if (!parsed || parsed.thread_id) {
            throw new ArgumentError('--url must be a Discord channel URL like https://discord.com/channels/<guild_id>/<channel_id>.');
        }
        return parsed;
    }

    const guildArg = stringArg(kwargs.guild);
    const channelArg = stringArg(kwargs.channel);
    if (!guildArg && !channelArg) {
        if (options.required) {
            throw new ArgumentError('Pass --url or --guild/--channel to choose a Discord channel.');
        }
        return null;
    }
    if (!channelArg) {
        throw new ArgumentError('Pass --channel with --guild, or use --url.');
    }

    let guildId = guildArg;
    if (guildArg && !isDiscordSnowflake(guildArg) && guildArg !== '@me') {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Use a two-segment channel URL of the form https://discord.com/channels/<guild_id>/<channel_id>.
  2. If you meant a thread/post, use resolveDiscordThreadTarget instead (or the thread-target command) which accepts the 3-segment URL.
  3. Run "opencli discord-app channels -f json", copy the exact channel URL, and retry with --url.
  4. Alternatively skip --url and pass --guild/--channel (numeric snowflake ids preferred).

Example fix

// before
await resolveDiscordChannelTarget(page, { url: 'https://discord.com/channels/123/456/789' }); // thread URL
// after
await resolveDiscordChannelTarget(page, { url: 'https://discord.com/channels/123/456' }); // channel URL
Defensive patterns

Strategy: validation

Validate before calling

function isChannelUrl(u) { const m = /^https:\/\/discord\.com\/channels\/(\d+)\/(\d+)$/.exec(String(u||'').trim()); return !!m && !m[3]; }
if (!isChannelUrl(opts.url)) throw new Error('Expected a 2-segment Discord channel URL');

Type guard

function isDiscordChannelUrl(v) { return typeof v === 'string' && /^https:\/\/discord\.com\/channels\/\d+\/\d+$/.test(v.trim()); }

Try / catch

try { const target = await resolveDiscordChannelTarget(page, { url }); } catch (e) { if (String(e.message).includes('--url must be')) { /* fall back to --guild/--channel or fix URL */ } else throw e; }

Prevention

When it happens

Trigger: Calling resolveDiscordChannelTarget (via the target/channelTarget commands) with kwargs.url set to a string that parseDiscordChannelUrl cannot parse (not matching https://discord.com/channels/<guild_id>/<channel_id>) or that parses with a thread_id (a 3-segment thread/post URL).

Common situations: Pasting a thread/message permalink instead of a channel URL; passing a bare channel name or id in --url; typos or missing the discord.com/channels prefix; copying a URL from a forum post or archived thread.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/618c6d81d5ea387f. Report an issue: GitHub.