{"record":{"id":"7fadf2722bbd7758","repo":"jackwener/OpenCLI","slug":"semanticscholar-citations-offset-must-be-a-non-neg","errorCode":null,"errorMessage":"semanticscholar citations offset must be a non-negative integer","messagePattern":"semanticscholar citations offset must be a non-negative integer","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/semanticscholar/citations.js","lineNumber":38,"sourceCode":"    name: 'citations',\n    access: 'read',\n    description: 'List papers that cite a Semantic Scholar paper (paginated)',\n    domain: 'api.semanticscholar.org',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [\n        { name: 'id', positional: true, required: true, help: 'paperId (40-char hex), DOI, arXiv id, or prefixed id' },\n        { name: 'limit', type: 'int', default: 20, help: 'Max citing papers (1-1000, single Semantic Scholar page)' },\n        { name: 'offset', type: 'int', default: 0, help: 'Page offset (0-based)' },\n    ],\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');","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/semanticscholar/citations.js#L20-L56","documentation":"The semanticscholar citations command (clis/semanticscholar/citations.js:38) validates the pagination offset client-side before building the API URL. An ArgumentError is thrown when the offset is missing-integer or negative — i.e. not a whole number >= 0. This is input validation, not a network issue.","triggerScenarios":"Calling `semanticscholar citations <id> --offset -5`, passing a non-numeric string like \"abc\" or \"1.5\", or a float/negative value via a script that forwards raw user input into args.offset.","commonSituations":"Pagination loops that decrement below zero; shell scripts interpolating unset variables (empty/negative values); confusing 0-based vs 1-based paging and passing offset=-1; passing strings like \"page3\" instead of integers.","solutions":["Pass a non-negative integer offset (0-based), e.g. --offset 0, 20, 40.","Fix the pagination loop so page N uses offset = N * limit with N >= 0.","Ensure the variable being interpolated is set and numeric before invoking the command.","Use the default (omit --offset) to start from the first page."],"exampleFix":"// before\nconst offset = Number(process.env.PAGE) - 2; // can go negative\nawait cli('semanticscholar citations', id, { offset });\n// after\nconst offset = Math.max(0, (Number(process.env.PAGE) - 1) * limit);\nawait cli('semanticscholar citations', id, { offset: Number.isInteger(offset) ? offset : 0 });","handlingStrategy":"validation","validationCode":"function safeOffset(raw) {\n  const n = typeof raw === 'number' ? raw : Number(raw);\n  if (!Number.isInteger(n) || n < 0) throw new TypeError(`offset must be a non-negative integer, got ${JSON.stringify(raw)}`);\n  return n;\n}\n// call before invoking:\nsafeOffset(args.offset ?? 0);","typeGuard":"function isValidOffset(v) {\n  const n = typeof v === 'number' ? v : Number(v);\n  return Number.isInteger(n) && n >= 0;\n}","tryCatchPattern":"try {\n  await cli('semanticscholar citations', id, { offset });\n} catch (err) {\n  if (/offset must be a non-negative integer/.test(err.message)) {\n    console.error(`Bad --offset value: ${JSON.stringify(offset)}; using 0`);\n    return cli('semanticscholar citations', id, { offset: 0 });\n  }\n  throw err;\n}","preventionTips":["Clamp and coerce pagination inputs at the script boundary before invoking the CLI.","Remember offset is 0-based; never derive it as (page - 2) arithmetic.","Guard against unset shell variables expanding to empty/negative values.","Unit-test pagination wrappers with edge inputs (0, -1, 'abc', 1.5)."],"tags":["validation","arguments","pagination","semanticscholar"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}