jackwener/OpenCLI · error · ArgumentError

Could not extract tweet ID from URL: ${value}

Error message

Could not extract tweet ID from URL: ${value}

What it means

parseTweetUrl validates tweet URLs in clis/twitter/shared.js:65. After confirming the URL is https and on an x.com/twitter.com host, it matches pathname against TWEET_PATH_PATTERN (^/(?:[^/]+|i)/status/(\d+)/?). If the path does not contain a /status/<numeric-id> segment, it throws ArgumentError with the offending URL. The library throws this because a tweet ID is required for every downstream GraphQL call.

Source

Thrown at clis/twitter/shared.js:65

export function parseTweetUrl(rawUrl) {
    const value = String(rawUrl ?? '').trim();
    if (!value) {
        throw new ArgumentError('twitter tweet URL cannot be empty', 'Example: opencli twitter retweet https://x.com/jack/status/20');
    }
    let parsed;
    try {
        parsed = new URL(value);
    }
    catch {
        throw new ArgumentError(`Invalid tweet URL: ${value}`, 'Use a full https://x.com/<user>/status/<id> URL');
    }
    const hostname = parsed.hostname.toLowerCase();
    if (parsed.protocol !== 'https:' || !isTwitterHost(hostname)) {
        throw new ArgumentError(`Invalid tweet URL host: ${value}`, 'Use a full https://x.com/<user>/status/<id> URL');
    }
    const match = parsed.pathname.match(TWEET_PATH_PATTERN);
    if (!match?.[1]) {
        throw new ArgumentError(`Could not extract tweet ID from URL: ${value}`, 'Use a full https://x.com/<user>/status/<id> URL');
    }
    return {
        id: match[1],
        url: parsed.toString(),
    };
}

/**
 * Build a JS source fragment that, when embedded inside a `page.evaluate(...)`
 * IIFE, declares browser-side helpers for scoping operations to a specific
 * tweet by status id. Sibling adapters historically inlined ad-hoc article
 * lookups that either (a) skipped scoping entirely (silent: act on first
 * matching button on a conversation page) or (b) used substring matches like
 * `pathname.includes('/status/' + tweetId)` (silent: `/status/123` matches
 * `/status/1234567`). This helper centralises the canonical pattern so all
 * write-actions reuse the same exact-match guard.
 *
 * Declared bindings (available to the embedding IIFE):

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open the tweet in a browser and copy the full permalink from the address bar or the tweet's share > Copy link button; it must look like https://x.com/<user>/status/<numeric-id>
  2. If you only have the tweet ID, construct the URL yourself: `https://x.com/i/status/${id}`
  3. Strip tracking suffixes carefully — the pattern requires the numeric ID directly after /status/, so `https://x.com/jack/status/12345?s=20` is fine but `https://x.com/jack/status/` is not
  4. Check that you did not paste a URL for a different entity (user profile, list, or search results page)

Example fix

// before
opencli twitter retweet https://x.com/jack
// after
opencli twitter retweet https://x.com/jack/status/1784239871234567
Defensive patterns

Strategy: validation

Validate before calling

function looksLikeTweetUrl(u) {
  try {
    const p = new URL(String(u).trim());
    if (p.protocol !== 'https:') return false;
    if (!/(^|\.)(x\.com|twitter\.com)$/.test(p.hostname)) return false;
    return /^\/(?:[^/]+|i)\/status\/(\d+)\/?$/.test(p.pathname);
  } catch { return false; }
}
if (!looksLikeTweetUrl(input)) throw new Error(`Not a tweet permalink: ${input}`);

Type guard

function isTweetUrl(value) {
  try {
    const p = new URL(String(value).trim());
    return p.protocol === 'https:' && /(^|\.)(x\.com|twitter\.com)$/.test(p.hostname)
      && /^\/(?:[^/]+|i)\/status\/\d+\/?$/.test(p.pathname);
  } catch { return false; }
}

Try / catch

import { ArgumentError } from '@jackwener/opencli/errors';
try {
  const { id } = await parseTweetUrl(url);
} catch (e) {
  if (e instanceof ArgumentError) {
    console.error(`Bad tweet URL: ${url}. ${e.hint ?? 'Use https://x.com/<user>/status/<id>'}`);
    process.exitCode = 2;
  } else throw e;
}

Prevention

When it happens

Trigger: Calling any command that accepts a tweet URL (e.g. opencli twitter retweet/tweet actions) with a URL whose pathname is not /<user>/status/<digits>, such as https://x.com/jack (profile), https://x.com/jack/status/ (missing id), https://x.com/jack/status/abc (non-numeric id), or a non-status deep link like /jack/likes/123.

Common situations: Pasting a profile page or photo page URL instead of the tweet permalink; copying a mobile or shortened link with extra segments (e.g. /i/web/status/ works but /jack/status followed by query junk stripped wrong); URLs from retweet quotes like /jack/status/123/retweets; hand-built URLs with placeholder IDs.

Related errors


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