{"record":{"id":"47c2013bf75e8dc2","repo":"jackwener/OpenCLI","slug":"semanticscholar-search-returned-an-unexpected-payl","errorCode":null,"errorMessage":"semanticscholar search returned an unexpected payload shape","messagePattern":"semanticscholar search returned an unexpected payload shape","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/semanticscholar/search.js","lineNumber":40,"sourceCode":"    access: 'read',\n    description: 'Search Semantic Scholar papers by free text',\n    domain: 'api.semanticscholar.org',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [\n        { name: 'query', positional: true, required: true, help: 'Search text (e.g. \"attention is all you need\", \"diffusion model\")' },\n        { name: 'limit', type: 'int', default: 20, help: 'Max papers (1-100, single Semantic Scholar page)' },\n    ],\n    columns: ['rank', 'paperId', 'doi', 'title', 'year', 'firstAuthor', 'citationCount', 'url'],\n    func: async (args) => {\n        const query = requireString(args.query, 'query');\n        const limit = requireBoundedInt(args.limit, 20, 100);\n        const url = `${S2_GRAPH_BASE}/paper/search?query=${encodeURIComponent(query)}&limit=${limit}&fields=${FIELDS}`;\n        const body = await s2Fetch(url, 'semanticscholar search');\n\n        const data = Array.isArray(body?.data) ? body.data : null;\n        if (data === null) {\n            throw new CommandExecutionError('semanticscholar search returned an unexpected payload shape');\n        }\n        if (!data.length) {\n            throw new EmptyResultError('semanticscholar search', `No Semantic Scholar papers matched \"${query}\".`);\n        }\n\n        return data.slice(0, limit).map((p, i) => normalizePaperRow(p, 'search', { rank: i + 1 }));\n    },\n});\n","sourceCodeStart":22,"sourceCodeEnd":49,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/semanticscholar/search.js#L22-L49","documentation":"The Semantic Scholar /paper/search endpoint is expected to return an object with a `data` array of paper results. This library throws a CommandExecutionError when the response JSON either is not an object or lacks an array-shaped `data` field, i.e. the payload does not match the documented Graph API search schema. It guards callers from dereferencing `body.data` on a malformed or schema-changed response.","triggerScenarios":"Calling `opencli semanticscholar search <query>` when the API responds 200 with JSON whose `data` property is missing, null, or not an array (e.g. an error envelope like `{message, error}` slipped past body.error checks, a proxy returned a different JSON shape, or Semantic Scholar changed the search response schema).","commonSituations":"Corporate proxies or captive portals injecting JSON error bodies; Semantic Scholar returning an API-level error object with a 200 status; API version drift (Graph v1 schema change); hitting a mirror/gateway that does not proxy the Graph API faithfully.","solutions":["Log the raw response body (e.g. curl the same URL with your SEMANTIC_SCHOLAR_API_KEY) and inspect the actual JSON shape to see what deviated.","Check status.semanticscholar.org / the Graph API changelog for a schema change to /paper/search and update the adapter or library.","Retry after a short wait if a transient gateway was substituting error bodies; verify network path to api.semanticscholar.org.","If a proxy is rewriting responses, bypass it or add it to NO_PROXY so the client talks to the API directly."],"exampleFix":"// before\nconst data = Array.isArray(body?.data) ? body.data : null;\nif (data === null) {\n    throw new CommandExecutionError('semanticscholar search returned an unexpected payload shape');\n}\n// after (log the offending payload to debug)\nconst data = Array.isArray(body?.data) ? body.data : null;\nif (data === null) {\n    throw new CommandExecutionError(\n        `semanticscholar search returned an unexpected payload shape: ${JSON.stringify(body).slice(0, 300)}`,\n    );\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isSearchPayload(body) {\n    return body != null && typeof body === 'object' && Array.isArray(body.data);\n}\n// usage: if (!isSearchPayload(body)) throw new CommandExecutionError('unexpected payload shape');","tryCatchPattern":"try {\n    const rows = await searchPapers(query);\n} catch (err) {\n    if (err instanceof CommandExecutionError && /unexpected payload shape/.test(err.message)) {\n        logger.error('S2 search schema drift', err.message);\n    } else throw err;\n}","preventionTips":["Pin and monitor the Graph API version you target; subscribe to Semantic Scholar API announcements.","Log raw response bodies on schema mismatch so drift is diagnosable.","Add a contract test that mocks /paper/search and asserts the `data` array shape.","Bypass intercepting proxies for API hosts to avoid shape-rewriting middleware."],"tags":["api","schema-validation","network"],"backgroundTag":"api-response-schema-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}