jackwener/OpenCLI · error · ArgumentError

twitter download username must be a valid Twitter/X handle

Error message

twitter download username must be a valid Twitter/X handle

What it means

After --tweet-url mode is ruled out, the positional username is passed through normalizeTwitterScreenName, which strips @/URL decorations and validates the handle. If nothing valid remains (empty string), the command throws an ArgumentError indicating the handle is invalid, with an example usage hint as remediation detail.

Source

Thrown at clis/twitter/download.js:334

    columns: ['index', 'tweet_id', 'url', 'type', 'status', 'size'],
    func: async (page, kwargs) => {
        try {
            const rawUsername = String(kwargs.username ?? '').trim();
            const tweetUrl = String(kwargs['tweet-url'] ?? '').trim();
            const output = kwargs.output;
            if (!rawUsername && !tweetUrl) {
                throw new ArgumentError('twitter download requires either <username> or --tweet-url');
            }
            if (rawUsername && tweetUrl) {
                throw new ArgumentError('Use either <username> or --tweet-url, not both');
            }
            if (tweetUrl) {
                return downloadSingleTweet(page, tweetUrl, output);
            }
            const limit = requireLimit(kwargs.limit);
            const username = normalizeTwitterScreenName(rawUsername);
            if (!username) {
                throw new ArgumentError('twitter download username must be a valid Twitter/X handle', 'Example: opencli twitter download @jack --limit 20');
            }
            return downloadUserMedia(page, username, limit, output);
        }
        catch (err) {
            if (err instanceof CliError) throw err;
            throw new CommandExecutionError(`twitter download failed: ${err?.message ?? String(err)}`);
        }
    },
});

async function downloadUserMedia(page, username, limit, output) {
    await page.goto(`https://x.com/${username}`);
    await page.wait({ selector: '[data-testid="primaryColumn"]' });

    const cookies = await page.getCookies({ url: 'https://x.com' });
    const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;
    if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Provide a valid handle, e.g. `opencli twitter download @jack --limit 20`
  2. Check that the shell variable holding the username is not empty before invoking
  3. If using a profile URL, reduce it to the handle yourself (x.com/jack -> @jack)

Example fix

// before
opencli twitter download ""
// after
opencli twitter download @jack --limit 20
Defensive patterns

Strategy: validation

Validate before calling

const handle = raw.replace(/^@/, '');
if (!/^[A-Za-z0-9_]{1,15}$/.test(handle)) {
  throw new Error(`Invalid Twitter handle: ${raw}`);
}

Type guard

const isValidHandle = (v) => typeof v === 'string' && /^[A-Za-z0-9_]{1,15}$/.test(v.replace(/^@/, ''));

Prevention

When it happens

Trigger: Running `opencli twitter download ''`, passing only whitespace, or a value that normalizeTwitterScreenName cannot reduce to a valid screen name (e.g. a full profile URL with no resolvable handle or garbage characters).

Common situations: Forgetting the positional argument entirely; quoting issues in shell producing an empty string; pasting a full x.com profile URL where the extractor yields nothing; variables interpolated empty in scripts.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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