jackwener/OpenCLI · error · CommandExecutionError
twitter download failed: ${err?.message ?? String(err)}
Error message
twitter download failed: ${err?.message ?? String(err)} What it means
The command wraps its body in try/catch: any thrown error that is not already a CliError is re-wrapped in a CommandExecutionError whose message is prefixed with 'twitter download failed:'. This catch-all surfaces unexpected failures (browser errors, fetch failures, network issues) with consistent CLI formatting.
Source
Thrown at clis/twitter/download.js:340
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)');
const userMediaOperation = await resolveTwitterOperationMetadata(page, 'UserMedia', USER_MEDIA_OPERATION);
const userByScreenNameOperation = await resolveTwitterOperationMetadata(page, 'UserByScreenName', USER_BY_SCREEN_NAME_OPERATION);
const headers = JSON.stringify({
'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
'X-Csrf-Token': ct0,View on GitHub (pinned to 49907e53dc)
Solutions
- Read the embedded original message after the prefix to identify the root cause
- Check network connectivity and that x.com is reachable from the browser
- Re-run with a fresh logged-in browser session; retry after rate-limit windows
- Update the CLI if x.com DOM/GraphQL changed and selectors or operations are stale
Defensive patterns
Strategy: try-catch
Try / catch
try {
await runTwitterDownload(args);
} catch (err) {
if (err.name === 'CommandExecutionError') {
console.error('Root cause:', err.message.replace(/^twitter download failed: /, ''));
}
} Prevention
- Ensure network access to x.com before running
- Keep the CLI updated against x.com changes
- Use a healthy, logged-in browser profile
When it happens
Trigger: Any non-CliError exception inside the download flow: page.goto failures, wait timeouts, page.evaluate syntax/runtime errors, requireFetchPayload rejections on non-OK fetches, or downloadTwitterMedia filesystem/ytdlp errors.
Common situations: No network or DNS failure reaching x.com; page load timeout; x.com UI changes breaking selectors; rate limiting causing non-OK HTTP responses; ytdlp missing or failing on video download.
Related errors
- 12306 whoami failed: ${probe.detail}
- --resume-file requires --all
- twitter collection --until must be an RFC3339 timestamp
- twitter collection --limit must be an integer between 1 and
- twitter collection --page-delay must be an integer between 0
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/9219fed77bd49de4.
Report an issue: GitHub.