{"record":{"id":"8cf8066b29a36692","repo":"jackwener/OpenCLI","slug":"twitter-search-limit-must-be-a-positive-integer","errorCode":null,"errorMessage":"twitter search --limit must be a positive integer","messagePattern":"twitter search --limit must be a positive integer","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/twitter/search.js","lineNumber":285,"sourceCode":"    browser: true,\n    args: [\n        { name: 'query', type: 'string', required: true, positional: true, help: 'Search query. Raw X operators (e.g. \"exact phrase\", #tag, OR, lang:en, since:YYYY-MM-DD, from:, since:) are passed through unchanged.' },\n        { name: 'filter', type: 'string', default: 'top', choices: ['top', 'live'], help: 'Legacy alias for --product. Kept for backwards compatibility; if --product is set it wins.' },\n        { name: 'product', type: 'string', choices: PRODUCT_CHOICES, help: 'Which X search tab to read: top (default), live (Latest), photos, videos. Maps to the f= URL param.' },\n        { name: 'from', type: 'string', help: 'Restrict to tweets authored by <user>. Leading @ is stripped. Equivalent to appending `from:<user>` to the query.' },\n        { name: 'has', type: 'string', choices: HAS_CHOICES, help: 'Restrict to tweets that have media|images|videos|links|replies. Maps to X\\'s `filter:<has>` operator.' },\n        { name: 'exclude', type: 'string', choices: EXCLUDE_CHOICES, help: 'Exclude tweets matching <type>: replies|retweets|media|links. Maps to X\\'s `-filter:<x>` operator (retweets → -filter:nativeretweets).' },\n        { name: 'limit', type: 'int', default: 15, help: 'Maximum number of tweets to return (default 15). Result count after server-side filtering.' },\n        { name: 'top-by-engagement', type: 'int', default: 0, help: 'When set to N>0, re-rank the results by weighted engagement (likes×1 + retweets×3 + replies×2 + bookmarks×5 + log10(views+1)×0.5) and return the top N. Default 0 keeps X\\'s native ordering.' },\n    ],\n    columns: ['id', 'author', 'bio', 'text', 'created_at', 'likes', 'views', 'url', 'has_media', 'media_urls', 'media_posters', 'card', 'quoted_tweet'],\n    func: async (page, kwargs) => {\n        const finalQuery = buildSearchQuery(kwargs.query, kwargs);\n        if (!finalQuery) {\n            throw new ArgumentError('twitter search query is empty', 'Provide a non-empty <query>, or use at least one of --from / --has / --exclude.');\n        }\n        if (!Number.isInteger(Number(kwargs.limit)) || Number(kwargs.limit) <= 0) {\n            throw new ArgumentError('twitter search --limit must be a positive integer', 'Example: opencli twitter search opencli --limit 15');\n        }\n        const cookies = await page.getCookies({ url: 'https://x.com' });\n        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;\n        if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');\n        await page.goto('https://x.com/home', { waitUntil: 'load', settleMs: 1000 });\n        const operation = await resolveTwitterOperationMetadata(page, 'SearchTimeline', SEARCH_TIMELINE_OPERATION);\n        const headers = JSON.stringify({\n            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,\n            'X-Csrf-Token': ct0,\n            'X-Twitter-Auth-Type': 'OAuth2Session',\n            'X-Twitter-Active-User': 'yes',\n            'Content-Type': 'application/json',\n        });\n        const product = resolveSearchProduct(kwargs);\n        const results = [];\n        const seen = new Set();\n        let cursor = null;\n        // Runaway guard only; --limit and cursor exhaustion control normal pagination.","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/twitter/search.js#L267-L303","documentation":"ArgumentError raised when the --limit value is not a positive integer. The library validates Number(kwargs.limit) is an integer > 0 before issuing the search request.","triggerScenarios":"`--limit 0`, `--limit -5`, `--limit abc`, `--limit 12.5`, or limit passed as an empty string — anything failing Number.isInteger(Number(v)) || v <= 0.","commonSituations":"Typos on the command line, copying fractional defaults like '10.0', environment/CI variables expanding to empty, or passing limit as a string with units ('15 tweets').","solutions":["Pass a whole number greater than 0, e.g. --limit 15","Remove units/decimals from the value","Ensure the CI variable supplying the limit is set and numeric"],"exampleFix":"// before\nopencli twitter search opencli --limit 0\n// after\nopencli twitter search opencli --limit 15","handlingStrategy":"validation","validationCode":"function isValidLimit(v) { const n = Number(v); return Number.isInteger(n) && n > 0; }\nif (!isValidLimit(process.env.SEARCH_LIMIT ?? kwargs.limit)) throw new Error('limit must be a positive integer');","typeGuard":"function isPositiveInt(v) { return Number.isInteger(Number(v)) && Number(v) > 0; }","tryCatchPattern":"try {\n  await opencli.twitter.search(q, { limit });\n} catch (e) {\n  if (e.name === 'ArgumentError' && /--limit/.test(e.message)) {\n    console.error('Use e.g. --limit 15');\n  } else throw e;\n}","preventionTips":["Pass whole numbers > 0 only","Strip units/decimals from CLI values","Ensure CI/env variables supplying the limit are set","Coerce and validate in your wrapper before calling"],"tags":["validation","argument","twitter","limit"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}