jackwener/OpenCLI · error · CommandExecutionError
twitter_collection_protocol_error
twitter_collection_protocol_error
Error message
twitter_collection_protocol_error: timeline post is missing a stable ID
What it means
extractCollectionPost unwraps each timeline entry's tweet result and requires a non-empty string rest_id; entries without a stable ID cannot be deduplicated or referenced downstream, so the library throws a protocol error indicating the payload violates its expected shape.
Source
Thrown at clis/twitter/collection.js:161
if (replyId || replyHandle || replyAuthorId) {
return {
kind: 'reply',
target: {
post_id: replyId,
author_handle: replyHandle,
author_id: replyAuthorId,
url: replyId && replyHandle ? `https://x.com/${replyHandle}/status/${replyId}` : null,
context_status: replyId ? 'unavailable' : 'unknown',
},
};
}
return { kind: 'original', target: null };
}
function extractCollectionPost(result, seen) {
const tweet = unwrapTweetResult(result);
if (!tweet?.rest_id || typeof tweet.rest_id !== 'string') {
throw new CommandExecutionError('twitter_collection_protocol_error: timeline post is missing a stable ID');
}
if (seen.has(tweet.rest_id)) return null;
seen.add(tweet.rest_id);
const legacy = tweet.legacy || {};
const user = tweet.core?.user_results?.result;
const author = user?.legacy?.screen_name || user?.core?.screen_name || null;
if (!author || !normalizeTwitterScreenName(author)) {
throw new CommandExecutionError('twitter_collection_protocol_error: timeline post is missing an author handle');
}
return {
id: tweet.rest_id,
author,
name: user?.legacy?.name || user?.core?.name || '',
text: tweet.note_tweet?.note_tweet_results?.result?.text || legacy.full_text || '',
likes: legacy.favorite_count || 0,
retweets: legacy.retweet_count || 0,
replies: legacy.reply_count || 0,
views: Number(tweet.views?.count) || 0,View on GitHub (pinned to 49907e53dc)
Solutions
- Update the CLI to match the current X GraphQL response shape
- Inspect the offending entry's JSON to see where the ID now lives and report/patch unwrapTweetResult
- Filter out tombstone/promoted entries upstream if your tooling wraps this library
- Re-run the collection to see if the malformed entry was transient
Example fix
null
Defensive patterns
Strategy: type-guard
Validate before calling
function entryHasStableId(entry) {
const tweet = unwrapTweetResult(entry);
return tweet != null && typeof tweet.rest_id === 'string' && tweet.rest_id.length > 0;
}
const safeEntries = entries.filter(entryHasStableId); Type guard
function hasRestId(t) {
return t != null && typeof t === 'object' && typeof t.rest_id === 'string' && t.rest_id.length > 0;
} Try / catch
try {
const post = extractCollectionPost(entry, seen);
} catch (err) {
if (String(err.message).startsWith('twitter_collection_protocol_error')) {
console.warn('skipping malformed timeline entry');
} else throw err;
} Prevention
- Filter tombstone/promoted/ad entries before parsing
- Keep the CLI in sync with current X GraphQL response shapes
- Log raw payloads of failing entries to detect shape drift early
- Dedupe on rest_id only after confirming it is a non-empty string
When it happens
Trigger: A timeline/collection entry contains an unwrapped tweet result lacking rest_id (or non-string rest_id) — e.g. tombstone entries, ad/promoted entries, or X GraphQL shape changes where the ID moved elsewhere in the object.
Common situations: New X API response versions with changed module wrappers; sponsored/tombstoned timeline modules in user timelines; parsing older cached payloads with a newer CLI expecting rest_id.
Related errors
- twitter_bookmarks_protocol_error
- twitter_bookmarks_repeated_cursor
- twitter_collection_unresolved_relationship
- Twitter UserMedia returned malformed user result
- Twitter UserMedia returned malformed timeline instructions
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/7ba002814bb2ad3d.
Report an issue: GitHub.