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
- Provide a valid handle, e.g. `opencli twitter download @jack --limit 20`
- Check that the shell variable holding the username is not empty before invoking
- 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
- Validate the handle matches /^[A-Za-z0-9_]{1,15}$/ before invoking
- Strip @ and URL prefixes yourself when accepting input from users
- Check shell variables are non-empty before interpolation
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
- --resume-file requires --all
- twitter download requires either <username> or --tweet-url
- Use either <username> or --tweet-url, not both
- --top-by-engagement cannot be combined with --output-file
- --output-file requires --all
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/4c34d06873c59055.
Report an issue: GitHub.