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

  1. Read the embedded original message after the prefix to identify the root cause
  2. Check network connectivity and that x.com is reachable from the browser
  3. Re-run with a fresh logged-in browser session; retry after rate-limit windows
  4. 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

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


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