{"record":{"id":"4bbc28bce716e2d7","repo":"jackwener/OpenCLI","slug":"maxrounds-must-be-a-positive-integer-got-json-s","errorCode":null,"errorMessage":"maxRounds must be a positive integer, got ${JSON.stringify(maxRounds)}","messagePattern":"maxRounds must be a positive integer, got (.+?)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/search.js","lineNumber":631,"sourceCode":"export function buildSearchExtractJs(webHost) {\n    return `\n      (() => {\n        const stripXhsAuthorDateSuffix = ${stripXhsAuthorDateSuffix.toString()};\n        const extractSearchRows = ${extractSearchRows.toString()};\n        return extractSearchRows(${JSON.stringify(webHost)});\n      })()\n    `;\n}\n\nexport function buildScrollHarvestJs(webHost, targetCount, options = {}) {\n    const maxRounds = options.maxRounds ?? 30;\n    const budgetMs = options.budgetMs ?? 30_000;\n    const step = options.step ?? DEFAULT_HARVEST_STEP;\n    if (!Number.isSafeInteger(targetCount) || targetCount < 1) {\n        throw new ArgumentError(`targetCount must be a positive integer, got ${JSON.stringify(targetCount)}`);\n    }\n    if (!Number.isSafeInteger(maxRounds) || maxRounds < 1) {\n        throw new ArgumentError(`maxRounds must be a positive integer, got ${JSON.stringify(maxRounds)}`);\n    }\n    if (!Number.isFinite(budgetMs) || budgetMs <= 0) {\n        throw new ArgumentError(`budgetMs must be a positive number, got ${JSON.stringify(budgetMs)}`);\n    }\n    if (!Number.isFinite(step) || step < 0) {\n        throw new ArgumentError(`step must be a non-negative number, got ${JSON.stringify(step)}`);\n    }\n    return `\n      (async () => {\n        const targetCount = ${targetCount};\n        const maxRounds = ${maxRounds};\n        const budgetMs = ${budgetMs};\n        const configuredStep = ${step};\n        const webHost = ${JSON.stringify(webHost)};\n        const noteUrlInfo = ${noteUrlInfo.toString()};\n        const mergeHarvestedRow = ${mergeHarvestedRow.toString()};\n        const stripXhsAuthorDateSuffix = ${stripXhsAuthorDateSuffix.toString()};\n        const extractSearchRows = ${extractSearchRows.toString()};","sourceCodeStart":613,"sourceCodeEnd":649,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/search.js#L613-L649","documentation":"buildScrollHarvestJs validates maxRounds as a safe integer >= 1 (default 30 via options.maxRounds) and throws this ArgumentError otherwise. maxRounds bounds how many scroll/harvest rounds the injected script performs, preventing infinite scrolling against an ever-loading feed.","triggerScenarios":"Passing options { maxRounds: 0 } (or negative/NaN/Infinity/float/string/undefined explicitly) to buildScrollHarvestJs.","commonSituations":"Config file supplying maxRounds: null; a caller that sets maxRounds from a computation that yields NaN; intending 0 to mean 'unlimited' but the API requires >= 1 and bounds come from budgetMs instead.","solutions":["Pass a positive safe integer in options, e.g. { maxRounds: 30 }.","Omit options.maxRounds to use the default of 30.","Validate config-derived values with Number.isSafeInteger before passing.","Use budgetMs (time budget) rather than maxRounds: 0 to limit harvesting."],"exampleFix":"// before\nbuildScrollHarvestJs(host, target, { maxRounds: config.rounds });\n// after\nconst maxRounds = Number.isSafeInteger(config.rounds) && config.rounds >= 1\n  ? config.rounds : 30;\nbuildScrollHarvestJs(host, target, { maxRounds });","handlingStrategy":"validation","validationCode":"function assertMaxRounds(v) {\n  if (!Number.isSafeInteger(v) || v < 1) {\n    throw new TypeError(`maxRounds must be a positive safe integer, got ${JSON.stringify(v)}`);\n  }\n}\nassertMaxRounds(options.maxRounds ?? 30);","typeGuard":"const isValidMaxRounds = (v) => v === undefined || (Number.isSafeInteger(v) && v >= 1);","tryCatchPattern":null,"preventionTips":["Omit maxRounds to accept the 30-round default unless tuning deliberately.","Use budgetMs for time-bounded harvesting instead of forcing tiny maxRounds values.","Validate config fields with Number.isSafeInteger before spreading into options.","Remember 0 is invalid here — do not use it to mean 'unlimited'.","Document the valid range (>=1) wherever options are constructed."],"tags":["argument-validation","typeerror","api-misuse"],"backgroundTag":"invalid-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}