{"record":{"id":"af83b88225581424","repo":"jackwener/OpenCLI","slug":"juejin-recommend-returned-a-malformed-has-more-fla","errorCode":null,"errorMessage":"juejin recommend returned a malformed has_more flag","messagePattern":"juejin recommend returned a malformed has_more flag","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/juejin/recommend.js","lineNumber":49,"sourceCode":"    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);\n        const cursor = requireCursor(args.cursor);\n        const payload = await juejinFetch(\n            '/recommend_api/v1/article/recommend_all_feed',\n            { id_type: 2, client_type: 2608, sort_type: 200, limit, cursor },\n            'juejin recommend',\n        );\n        const data = readDataArray(payload, 'juejin recommend');\n        const nextCursor = readResponseCursor(payload.cursor);\n        if (payload.has_more != null && typeof payload.has_more !== 'boolean') {\n            throw new CommandExecutionError('juejin recommend returned a malformed has_more flag');\n        }\n        const hasMore = payload.has_more == null ? '' : String(payload.has_more);\n        if (payload.has_more === true && !nextCursor) {\n            throw new CommandExecutionError('juejin recommend returned has_more without a next cursor');\n        }\n        return data.slice(0, limit).map((row, i) => ({\n            ...mapFeedItem(row, i + 1),\n            next_cursor: nextCursor,\n            has_more: hasMore,\n        }));\n    },\n});\n","sourceCodeStart":31,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/juejin/recommend.js#L31-L62","documentation":"The recommend command validates that the has_more field in the Juejin API response, when present, is strictly a boolean. If the API returns has_more as a number, string, or other type, a CommandExecutionError is thrown because the CLI cannot reliably translate it into the ''/'true'/'false' string output format. This guards against silently misreporting pagination state.","triggerScenarios":"Running the juejin recommend command when the response payload contains has_more with a non-boolean, non-null value (e.g. has_more: 1, has_more: \"true\", has_more: null is allowed but 0 or '1' is not).","commonSituations":"API version drift where Juejin starts returning has_more as 0/1 integers; a proxy or transformer middleware coercing booleans to strings; fixtures recorded from a different API version.","solutions":["Retry the request — if it is a transient/proxied corruption, a fresh call may return the correct shape.","Verify the current Juejin recommend API response schema; if has_more became an integer flag, update the validation to accept numbers 0/1.","Pin or update the CLI to the version matching the live API shape.","Log the raw payload to confirm the actual type of has_more before reporting an upstream bug."],"exampleFix":"// before (boolean-only)\nif (payload.has_more != null && typeof payload.has_more !== 'boolean') {\n    throw new CommandExecutionError('juejin recommend returned a malformed has_more flag');\n}\n// after (accept 0/1)\nconst hm = payload.has_more;\nif (hm != null && typeof hm !== 'boolean' && hm !== 0 && hm !== 1) {\n    throw new CommandExecutionError('juejin recommend returned a malformed has_more flag');\n}","handlingStrategy":"type-guard","validationCode":"if (payload.has_more !== undefined && payload.has_more !== null && typeof payload.has_more !== 'boolean') { throw new Error('unexpected has_more type: ' + typeof payload.has_more); }","typeGuard":"function hasValidHasMore(p){ return p.has_more == null || typeof p.has_more === 'boolean'; }","tryCatchPattern":"try {\n  return cliRecommend({ limit, cursor });\n} catch (e) {\n  if (/malformed has_more/.test(e.message)) return { items: [], has_more: false, degraded: true };\n  throw e;\n}","preventionTips":["Assert the API response shape once at the boundary (e.g. with zod: z.object({ has_more: z.boolean().optional(), cursor: z.unknown().optional() })).","Re-record API fixtures after any Juejin API version change.","Do not run responses through middlewares that coerce JSON booleans to strings/numbers."],"tags":["pagination","api-response","type-mismatch","cli"],"backgroundTag":"malformed-pagination-cursor","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}