jackwener/OpenCLI · error · CommandExecutionError
Twitter UserMedia returned a tweet without rest_id
Error message
Twitter UserMedia returned a tweet without rest_id
What it means
parseUserMedia recursively walks timeline instructions; when it finds a tweet_results.result node it unwraps TweetWithVisibilityResults and requires the resulting tweet object to have a rest_id (string or number). If the tweet node exists but has no rest_id, it throws this error instead of silently skipping the entry. This indicates a tweet-shaped node the parser cannot key on.
Source
Thrown at clis/twitter/download.js:273
const items = [];
let nextCursor = null;
const result = requireUserMediaPayload(data).data.user.result;
const instructionSets = [
result.timeline_v2?.timeline?.instructions,
result.timeline?.timeline?.instructions,
].filter(Array.isArray);
const instructions = instructionSets.flat();
const visit = (value) => {
if (!value || typeof value !== 'object') return;
if (value.type === 'TimelinePinEntry') return;
if (value.tweet_results?.result) {
const raw = value.tweet_results.result;
const tw = raw.__typename === 'TweetWithVisibilityResults' && raw.tweet
? raw.tweet
: (raw.tweet || raw);
const tweetId = typeof tw.rest_id === 'string' || typeof tw.rest_id === 'number' ? String(tw.rest_id) : '';
if (!tweetId) {
throw new CommandExecutionError('Twitter UserMedia returned a tweet without rest_id');
}
if (!seen.has(tweetId)) {
seen.add(tweetId);
const { media_urls } = extractMedia(tw.legacy || {});
for (const url of media_urls) {
items.push({ tweet_id: tweetId, url, type: classifyMediaUrl(url) });
}
}
}
if (
(value.entryType === 'TimelineTimelineCursor' || value.__typename === 'TimelineTimelineCursor')
&& (value.cursorType === 'Bottom' || value.cursorType === 'ShowMore')
&& value.value
) {
nextCursor = value.value;
}
if (Array.isArray(value)) {
for (const item of value) visit(item);View on GitHub (pinned to 49907e53dc)
Solutions
- Update the unwrap logic in visit() to handle new __typename variants that carry rest_id in a nested field.
- Skip (rather than throw) tweet nodes without rest_id if tombstones should not abort a full profile download — wrap the tweetId check in a continue.
- Re-run with an authenticated session that can see the gated content, if the missing rest_id stems from age/sensitive-content gating.
- Log the offending node's __typename to confirm which variant is appearing and extend parsing for it.
Example fix
// before
if (!tweetId) {
throw new CommandExecutionError('Twitter UserMedia returned a tweet without rest_id');
}
// after: skip tombstoned entries instead of failing the whole scan
if (!tweetId) {
return; // or log-and-continue in visit()
} Defensive patterns
Strategy: type-guard
Validate before calling
// Pre-filter nodes lacking rest_id before strict parsing
const usable = node?.tweet_results?.result
&& typeof (node.tweet_results.result.tweet ?? node.tweet_results.result).rest_id !== 'undefined'; Type guard
function hasRestId(raw) {
const tw = raw?.__typename === 'TweetWithVisibilityResults' && raw.tweet ? raw.tweet : (raw?.tweet || raw);
return typeof tw?.rest_id === 'string' || typeof tw?.rest_id === 'number';
} Try / catch
try {
await twitterDownload(username);
} catch (err) {
if (err instanceof CommandExecutionError && err.message.includes('without rest_id')) {
// degrade gracefully: skip unparseable tweet nodes and continue the scan
} else throw err;
} Prevention
- Handle TweetWithVisibilityResults and any new __typename variants in unwrapping
- Expect tombstoned/withheld/age-gated tweets on large profiles and skip them
- Use a fully authenticated session so gated content resolves to real tweet nodes
- Log the node __typename when rest_id is missing to spot new schema variants
When it happens
Trigger: A tweet_results.result node in the UserMedia timeline lacks rest_id — e.g. tombstoned/deleted tweets, withheld or age-gated content nodes, availability-limited entries, or a new __typename variant not handled by the TweetWithVisibilityResults unwrap.
Common situations: Scraping a profile containing deleted/tombstoned tweets; tweets withheld in your region or behind sensitive-content gating; X introducing new result __typename shapes; inconsistent payloads for unauthenticated/limited sessions.
Related errors
- Twitter UserMedia returned GraphQL errors: ${JSON.stringify(
- Malformed Twitter follower: missing screen_name
- ${probe.detail}
- Barchart greeks returned an unreadable options payload${data
- PARSE_ERROR
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/9612cb69fcf8029f.
Report an issue: GitHub.