{"record":{"id":"533fba8e52f48870","repo":"jackwener/OpenCLI","slug":"semanticscholar-citations-row-is-missing-citingpap","errorCode":null,"errorMessage":"semanticscholar citations row is missing citingPaper","messagePattern":"semanticscholar citations row is missing citingPaper","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/semanticscholar/citations.js","lineNumber":56,"sourceCode":"            throw new ArgumentError('semanticscholar citations offset must be a non-negative integer');\n        }\n        if (offset > 9999) {\n            throw new ArgumentError('semanticscholar citations offset must be <= 9999');\n        }\n        const url = `${S2_GRAPH_BASE}/paper/${encodeURIComponent(ref)}/citations?fields=${FIELDS}&limit=${limit}&offset=${offset}`;\n        const body = await s2Fetch(url, 'semanticscholar citations');\n\n        const data = Array.isArray(body?.data) ? body.data : null;\n        if (data === null) {\n            throw new CommandExecutionError('semanticscholar citations returned an unexpected payload shape');\n        }\n        if (!data.length) {\n            throw new EmptyResultError('semanticscholar citations', `No Semantic Scholar citations for \"${args.id}\" at offset ${offset}.`);\n        }\n\n        return data.slice(0, limit).map((entry, i) => {\n            if (!entry || typeof entry !== 'object' || !('citingPaper' in entry)) {\n                throw new CommandExecutionError('semanticscholar citations row is missing citingPaper');\n            }\n            return normalizePaperRow(entry.citingPaper, 'citations', { rank: offset + i + 1 });\n        });\n    },\n});\n","sourceCodeStart":38,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/semanticscholar/citations.js#L38-L62","documentation":"The citations endpoint returns rows shaped `{ data: [{ citingPaper: {...} }] }`. Before unwrapping, each row is checked (clis/semanticscholar/citations.js:56): if an entry is null, not an object, or lacks a `citingPaper` key, this CommandExecutionError is thrown. It indicates the API violated its documented per-row contract.","triggerScenarios":"A row in body.data is null or a primitive; the API returns citations entries keyed differently (schema drift); partial/soft-error bodies that happen to include a top-level data array but not the citingPaper wrapper.","commonSituations":"Semantic Scholar A/B-testing or rolling out an API schema change; degraded/partial responses during incidents where rows come back null; a cached or proxied response with an older/newer shape.","solutions":["Dump the raw response (curl the same URL) and inspect the offending row's shape.","Retry — transient partial responses during S2 incidents usually resolve.","Check the Semantic Scholar API docs/changelog for a citations response schema change and update the adapter.","Filter or skip malformed rows client-side if you control the calling code.","Report persistent occurrences with the paper id to Semantic Scholar support."],"exampleFix":"// before\nreturn data.map((entry) => normalizePaperRow(entry.citingPaper, 'citations'));\n// after\nreturn data\n  .filter((entry) => entry && typeof entry === 'object' && 'citingPaper' in entry)\n  .map((entry) => normalizePaperRow(entry.citingPaper, 'citations'));","handlingStrategy":"type-guard","validationCode":"function rowHasCitingPaper(entry) {\n  return Boolean(entry) && typeof entry === 'object' && 'citingPaper' in entry;\n}\n// filter before mapping:\nconst usable = data.filter(rowHasCitingPaper);","typeGuard":"function isCitationEntry(entry) {\n  return typeof entry === 'object' && entry !== null\n    && 'citingPaper' in entry\n    && typeof entry.citingPaper === 'object'\n    && entry.citingPaper !== null\n    && typeof entry.citingPaper.title === 'string';\n}","tryCatchPattern":"try {\n  rows = await citations(id, offset);\n} catch (err) {\n  if (/missing citingPaper/.test(err.message)) {\n    console.error('S2 citations row schema violation; inspecting raw payload...');\n    // fetch raw body for diagnosis, retry once\n    return citations(id, offset);\n  }\n  throw err;\n}","preventionTips":["Validate rows defensively (filter instead of throwing) in your own wrappers.","Keep an eye on Semantic Scholar API changelogs for citations schema changes.","Retry once on row-level contract violations — they are often incident-related partial responses.","Save a sample raw response per paper id when reporting persistent issues."],"tags":["api","schema","semanticscholar","data-integrity"],"backgroundTag":"unexpected-api-payload-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}