{"record":{"id":"a176d25f605ef289","repo":"jackwener/OpenCLI","slug":"twitter-collection-invalid-timestamp","errorCode":"twitter_collection_invalid_timestamp","errorMessage":"twitter_collection_invalid_timestamp: post ${post.id}","messagePattern":"twitter_collection_invalid_timestamp: post (.+?)","errorType":"error_code","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/twitter/collection.js","lineNumber":240,"sourceCode":"        ) {\n            nextCursor = value.value;\n        }\n        if (Array.isArray(value)) {\n            for (const item of value) visit(item);\n            return;\n        }\n        for (const child of Object.values(value)) {\n            if (child && typeof child === 'object') visit(child);\n        }\n    };\n    for (const instructions of instructionSets) visit(instructions);\n    return [posts, nextCursor];\n}\n\nfunction parseCreatedAt(post) {\n    const parsed = new Date(post.created_at);\n    if (typeof post.created_at !== 'string' || !post.created_at || Number.isNaN(parsed.getTime())) {\n        throw new CommandExecutionError(`twitter_collection_invalid_timestamp: post ${post.id}`);\n    }\n    return parsed;\n}\n\nfunction completedReceipt(stopReason, until, pagesFetched, oldestSeenAt) {\n    return {\n        completed: true,\n        stop_reason: stopReason,\n        requested_until: until.toISOString(),\n        pages_fetched: pagesFetched,\n        oldest_seen_at: oldestSeenAt ? oldestSeenAt.toISOString() : null,\n    };\n}\n\nasync function paginateCollection({ until, limit, maxPages = MAX_USER_TWEETS_PAGES, fetchPage, wait }) {\n    const seen = new Set();\n    const seenCursors = new Set();\n    const posts = [];","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/collection.js#L222-L258","documentation":"parseCreatedAt validates each post's created_at field: it must be a non-empty string parseable to a valid Date. Posts without a usable timestamp cannot be compared against the 'until' cutoff, so completion of the collection can't be proven and the library throws this error naming the offending post id.","triggerScenarios":"A UserTweets entry whose tweet.legacy.created_at is missing, empty, or malformed (e.g. ad/promoted entries, tombstones reshaped into post objects, or X renaming legacy fields).","commonSituations":"Promoted/ad tweets injected into the timeline lacking legacy.created_at; X schema change renaming created_at; posts from retweets/quote cards with truncated legacy objects; account locale changes altering date format unexpectedly.","solutions":["Skip or whitelist promoted/ad entries and tombstones in the visit() walker before calling parseCreatedAt.","Log the offending post.id and its raw object to confirm which entry type lacks created_at.","Add a fallback timestamp source (e.g. tweet.core.legacy.created_at or sort index) if X moved the field.","Update the library if X changed the legacy payload shape.","Retry pagination — transient timeline injections usually disappear on the next page fetch."],"exampleFix":"// before\nconst createdAt = parseCreatedAt(post);\n// after\nif (!post?.created_at || Number.isNaN(new Date(post.created_at).getTime())) {\n  console.warn('skipping post without timestamp', post?.id);\n  continue;\n}\nconst createdAt = parseCreatedAt(post);","handlingStrategy":"type-guard","validationCode":"const bad = posts.filter(p => typeof p?.created_at !== 'string' || !p.created_at || Number.isNaN(new Date(p.created_at).getTime()));\nif (bad.length) console.warn('posts lacking valid created_at:', bad.map(p => p.id));","typeGuard":"function hasValidCreatedAt(post) {\n  return typeof post?.created_at === 'string' && post.created_at.length > 0\n    && !Number.isNaN(new Date(post.created_at).getTime());\n}","tryCatchPattern":"try {\n  const createdAt = parseCreatedAt(post);\n} catch (err) {\n  if (String(err.message).startsWith('twitter_collection_invalid_timestamp')) {\n    const id = String(err.message).split('post ')[1];\n    console.warn('skipping malformed post', id);\n  } else throw err;\n}","preventionTips":["Filter promoted/ad/tombstone entries before timestamp parsing.","Validate created_at is a parseable string for every entry you walk.","Support alternate timestamp locations (tweet.core.legacy.created_at) as fallback.","Keep the library updated when X renames legacy fields.","Log offending post ids to identify which entry types lack timestamps."],"tags":["twitter","validation","timestamp","pagination"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}