{"record":{"id":"7ba002814bb2ad3d","repo":"jackwener/OpenCLI","slug":"twitter-collection-protocol-error","errorCode":"twitter_collection_protocol_error","errorMessage":"twitter_collection_protocol_error: timeline post is missing a stable ID","messagePattern":"twitter_collection_protocol_error: timeline post is missing a stable ID","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/collection.js","lineNumber":161,"sourceCode":"    if (replyId || replyHandle || replyAuthorId) {\n        return {\n            kind: 'reply',\n            target: {\n                post_id: replyId,\n                author_handle: replyHandle,\n                author_id: replyAuthorId,\n                url: replyId && replyHandle ? `https://x.com/${replyHandle}/status/${replyId}` : null,\n                context_status: replyId ? 'unavailable' : 'unknown',\n            },\n        };\n    }\n    return { kind: 'original', target: null };\n}\n\nfunction extractCollectionPost(result, seen) {\n    const tweet = unwrapTweetResult(result);\n    if (!tweet?.rest_id || typeof tweet.rest_id !== 'string') {\n        throw new CommandExecutionError('twitter_collection_protocol_error: timeline post is missing a stable ID');\n    }\n    if (seen.has(tweet.rest_id)) return null;\n    seen.add(tweet.rest_id);\n    const legacy = tweet.legacy || {};\n    const user = tweet.core?.user_results?.result;\n    const author = user?.legacy?.screen_name || user?.core?.screen_name || null;\n    if (!author || !normalizeTwitterScreenName(author)) {\n        throw new CommandExecutionError('twitter_collection_protocol_error: timeline post is missing an author handle');\n    }\n    return {\n        id: tweet.rest_id,\n        author,\n        name: user?.legacy?.name || user?.core?.name || '',\n        text: tweet.note_tweet?.note_tweet_results?.result?.text || legacy.full_text || '',\n        likes: legacy.favorite_count || 0,\n        retweets: legacy.retweet_count || 0,\n        replies: legacy.reply_count || 0,\n        views: Number(tweet.views?.count) || 0,","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/collection.js#L143-L179","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"null","handlingStrategy":"type-guard","validationCode":"function entryHasStableId(entry) {\n  const tweet = unwrapTweetResult(entry);\n  return tweet != null && typeof tweet.rest_id === 'string' && tweet.rest_id.length > 0;\n}\nconst safeEntries = entries.filter(entryHasStableId);","typeGuard":"function hasRestId(t) {\n  return t != null && typeof t === 'object' && typeof t.rest_id === 'string' && t.rest_id.length > 0;\n}","tryCatchPattern":"try {\n  const post = extractCollectionPost(entry, seen);\n} catch (err) {\n  if (String(err.message).startsWith('twitter_collection_protocol_error')) {\n    console.warn('skipping malformed timeline entry');\n  } else throw err;\n}","preventionTips":["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"],"tags":["twitter","protocol","schema-change","timeline"],"backgroundTag":"protocol-shape-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}