{"record":{"id":"bcdf41e94a6352af","repo":"jackwener/OpenCLI","slug":"webhost-feed-feed-item-id-is-missing-xsecto","errorCode":null,"errorMessage":"${webHost} feed: feed item ${id} is missing xsecToken; cannot build a signed drill-down URL","messagePattern":"(.+?) feed: feed item (.+?) is missing xsecToken; cannot build a signed drill-down URL","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/feed.js","lineNumber":126,"sourceCode":"        throw new CommandExecutionError(`${webHost} feed: ${data.error}`, `The SPA may still be hydrating; reload ${webHost}/explore and retry.`);\n    }\n    if (!Array.isArray(data.items)) {\n        throw new CommandExecutionError(`${webHost} feed: unexpected items payload shape`);\n    }\n    const rows = [];\n    for (const row of data.items) {\n        if (rows.length >= limit)\n            break;\n        if (!row || typeof row !== 'object') {\n            throw new CommandExecutionError(`${webHost} feed: malformed feed item`);\n        }\n        const id = toCleanString(row.id);\n        if (!id) {\n            throw new CommandExecutionError(`${webHost} feed: feed item is missing note id`);\n        }\n        const xsecToken = toCleanString(row.xsecToken);\n        if (!xsecToken) {\n            throw new CommandExecutionError(`${webHost} feed: feed item ${id} is missing xsecToken; cannot build a signed drill-down URL`);\n        }\n        rows.push({\n            id,\n            title: toCleanString(row.title),\n            type: toCleanString(row.type),\n            author: toCleanString(row.author),\n            likes: toCleanString(row.likes),\n            url: buildFeedNoteUrl(webHost, id, xsecToken),\n        });\n    }\n    if (rows.length === 0) {\n        throw new EmptyResultError(`${webHost}/feed`, 'No feed items in the hydrated store.');\n    }\n    return rows;\n}\n\nexport const command = cli({\n    site: 'xiaohongshu',","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/feed.js#L108-L144","documentation":"Feed items also require an xsecToken; runFeed throws this error when toCleanString(row.xsecToken) is empty, because signed drill-down URLs for note detail cannot be constructed without it.","triggerScenarios":"A row has a valid id but no xsecToken field (empty/undefined), commonly when the feed payload variant differs (e.g. different entry point or a site change moving the token).","commonSituations":"Site changes renaming xsecToken (e.g. xsec_token), feeds fetched from a context that doesn't issue tokens, promoted cards lacking tokens, partial hydration dropping the token field.","solutions":["Inspect the row in DevTools to find the current token field name and update FEEDS_READ_JS/mapping (e.g. row.xsec_token)","Retry after full hydration so tokens are populated","Skip token-less items and build URLs only for complete rows","Re-fetch the feed via the same explore entry point that includes xsecToken"],"exampleFix":"// before\nconst xsecToken = toCleanString(row.xsecToken);\n// after (tolerate alternate key)\nconst xsecToken = toCleanString(row.xsecToken ?? row.xsec_token);","handlingStrategy":"validation","validationCode":"const drillable = (data.items ?? []).filter(r => r && toCleanString(r.xsecToken ?? r.xsec_token) && toCleanString(r.id));","typeGuard":"function hasXsecToken(row) { return !!row && typeof row === 'object' && Boolean(toCleanString(row.xsecToken ?? row.xsec_token)); }","tryCatchPattern":"try {\n  const rows = await runFeed(page, webHost, limit);\n} catch (err) {\n  if (err instanceof CommandExecutionError && err.message.includes('xsecToken')) {\n    // skip token-less items or re-fetch feed from the entry point that issues tokens\n  } else throw err;\n}","preventionTips":["Map both xsecToken and xsec_token keys in the parser","Re-fetch the feed when tokens are missing; tokens come with the explore payload","Wait for full hydration before reading items","Skip promoted/placeholder cards that lack tokens"],"tags":["feed","schema","signed-url"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}