{"record":{"id":"90196c3638386720","repo":"jackwener/OpenCLI","slug":"juejin-recommend-returned-a-malformed-cursor","errorCode":null,"errorMessage":"juejin recommend returned a malformed cursor","messagePattern":"juejin recommend returned a malformed cursor","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/juejin/recommend.js","lineNumber":21,"sourceCode":"// Hits the `recommend_all_feed` endpoint, which mirrors what the Juejin web UI\n// renders on the front page; `sort_type` 200 is the default \"recommended\" mix.\nimport { cli, Strategy } from '@jackwener/opencli/registry';\nimport { CommandExecutionError } from '@jackwener/opencli/errors';\nimport {\n    juejinFetch,\n    mapFeedItem,\n    readDataArray,\n    requireBoundedInt,\n    requireCursor,\n} from './utils.js';\n\nfunction readResponseCursor(value) {\n    if (value == null || value === '') return '';\n    try {\n        return requireCursor(value);\n    }\n    catch {\n        throw new CommandExecutionError('juejin recommend returned a malformed cursor');\n    }\n}\n\ncli({\n    site: 'juejin',\n    name: 'recommend',\n    access: 'read',\n    description: 'Juejin (掘金) homepage recommended article feed',\n    domain: 'api.juejin.cn',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [\n        { name: 'limit', type: 'int', default: 20, help: 'Max articles (1-100, single page).' },\n        { name: 'cursor', type: 'string', default: '0', help: 'Pagination cursor; pass back the previous response\\'s next-page cursor to keep scrolling.' },\n    ],\n    columns: ['rank', 'article_id', 'title', 'brief', 'views', 'likes', 'comments', 'author', 'tags', 'url', 'next_cursor', 'has_more'],\n    func: async (args) => {\n        const limit = requireBoundedInt(args.limit, 20, 100);","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/juejin/recommend.js#L3-L39","documentation":"The Juejin recommend CLI wraps its pagination cursor parser (requireCursor) in a try/catch inside readResponseCursor. When the API response contains a cursor value that cannot be parsed as a valid non-negative decimal integer cursor, the underlying ArgumentError is replaced with a CommandExecutionError indicating the remote service returned malformed data. This signals an unexpected API response shape rather than a caller mistake.","triggerScenarios":"Calling the juejin recommend command when the upstream API returns a payload whose cursor field is a non-empty value that fails requireCursor validation, e.g. a string with non-digit characters, a negative number, a float, an object, or a boolean.","commonSituations":"Juejin changes its cursor format (e.g. switches to opaque base64 tokens or signed cursors); a proxy or cache returns an HTML error page parsed as JSON with a string cursor; scraping/CDN interposition alters response fields; a mocked or recorded fixture uses a placeholder cursor value like '<cursor>' or 'abc'.","solutions":["Retry the command once — transient CDN/proxy corruption can produce a bad payload; a fresh request often returns a valid cursor.","Check whether the juejin API changed its cursor format; update the CLI's requireCursor regex/parser to accept the new format.","Pass an explicit --cursor argument with a known-good numeric cursor from a previous successful response to bypass the malformed server cursor.","Update the CLI package if a newer version adapts to the new API response shape.","Report/inspect the raw payload (log payload.cursor) to confirm what the server actually returned before filing a bug."],"exampleFix":"// before (strict numeric-only cursor parsing)\nif (typeof raw === 'string' && /^(0|[1-9]\\d*)$/.test(raw)) return raw;\n// after (accept opaque server cursors)\nif (typeof raw === 'string' && /^[A-Za-z0-9_-]+$/.test(raw)) return raw;","handlingStrategy":"try-catch","validationCode":"function isPlausibleCursor(v){ return v == null || v === '' || (typeof v === 'string' && /^[A-Za-z0-9]+$/.test(v)) || (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0); }","typeGuard":"function isUsableCursor(v){ return (typeof v === 'string' && /^(0|[1-9]\\d*)$/.test(v)) || (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0) || v == null || v === ''; }","tryCatchPattern":"try {\n  const page = cliRecommend({ limit: 20, cursor });\n} catch (e) {\n  if (/malformed cursor/.test(e.message)) {\n    cursor = '0'; // restart pagination from the beginning\n    const page = cliRecommend({ limit: 20, cursor });\n  } else throw e;\n}","preventionTips":["Always feed next_cursor from a previous response back verbatim instead of constructing cursors yourself.","Validate payload.cursor shape before passing it onward in pipelines.","Pin the CLI version and watch for API format changes; test pagination against a recorded fixture.","Wrap pagination loops to reset to cursor '0' on malformed-cursor errors to avoid hard failures."],"tags":["pagination","cursor","api-response","cli"],"backgroundTag":"malformed-pagination-cursor","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}