{"record":{"id":"3222c17c7f7e88cf","repo":"jackwener/OpenCLI","slug":"semanticscholar-citations-returned-an-unexpected-p","errorCode":null,"errorMessage":"semanticscholar citations returned an unexpected payload shape","messagePattern":"semanticscholar citations returned an unexpected payload shape","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/semanticscholar/citations.js","lineNumber":48,"sourceCode":"    ],\n    columns: ['rank', 'paperId', 'doi', 'title', 'year', 'firstAuthor', 'citationCount', 'url'],\n    func: async (args) => {\n        const ref = requirePaperRef(args.id);\n        const limit = requireBoundedInt(args.limit, 20, 1000);\n        const offsetRaw = args.offset ?? 0;\n        const offset = typeof offsetRaw === 'number' ? offsetRaw : Number(offsetRaw);\n        if (!Number.isInteger(offset) || offset < 0) {\n            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":30,"sourceCodeEnd":62,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/semanticscholar/citations.js#L30-L62","documentation":"After a successful s2Fetch, the citations command (clis/semanticscholar/citations.js:48) expects the Semantic Scholar `/paper/{ref}/citations` payload to contain a `data` array. If `body.data` is missing or not an array, it throws this CommandExecutionError because the response cannot be interpreted as a citations page.","triggerScenarios":"The API returned 200 with JSON that is not the expected envelope — e.g. an error object like {error: \"...\"} or {message: \"...\"} served with 200, a rate-limit/throttling body, or an API schema change where citations are keyed differently.","commonSituations":"Semantic Scholar serving soft errors (throttling or maintenance notices) with HTTP 200; hitting an API version whose response shape changed; a proxy returning its own JSON error document; using an unsupported paper reference that yields an error envelope.","solutions":["Log/print the raw response body to see what the API actually returned instead of {data: [...]} .","Retry after a delay — Semantic Scholar frequently returns soft error bodies with 200 when throttled.","Verify the paper id/DOI/arXiv ref is valid (test with `semanticscholar paper <id>`).","Check Semantic Scholar API status/changelog for a response-shape change and update the adapter.","Check whether an intermediary (proxy) is substituting its own JSON error payload."],"exampleFix":"// before\nconst body = await s2Fetch(url, 'semanticscholar citations');\n// after\nconst body = await s2Fetch(url, 'semanticscholar citations');\nif (body && typeof body === 'object' && body.error) {\n  throw new Error(`S2 soft error: ${body.error} — retry later`);\n}\n// then proceed to body.data handling","handlingStrategy":"type-guard","validationCode":"// Validate the envelope as soon as you have the body:\nfunction hasCitationsEnvelope(body) {\n  return body !== null && typeof body === 'object' && Array.isArray(body.data);\n}","typeGuard":"function isCitationsPayload(body) {\n  return typeof body === 'object' && body !== null\n    && Array.isArray(body.data)\n    && body.data.every((e) => e && typeof e === 'object' && 'citingPaper' in e);\n}","tryCatchPattern":"try {\n  rows = await citations(id, offset);\n} catch (err) {\n  if (/unexpected payload shape/.test(err.message)) {\n    await sleep(1000);\n    rows = await citations(id, offset); // soft-error bodies with 200 are often transient\n  } else throw err;\n}","preventionTips":["Log the raw response body when the shape check fails to diagnose soft errors.","Back off and retry once before surfacing the failure to users.","Pin/track the Semantic Scholar API version you depend on and watch its changelog.","Keep validation of body.data centralized in one helper so schema drift is caught in one place."],"tags":["api","schema","semanticscholar","response-shape"],"backgroundTag":"unexpected-api-payload-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}