{"record":{"id":"6fe52201efabe78c","repo":"jackwener/OpenCLI","slug":"pubmed-year-from-must-be-year-to","errorCode":null,"errorMessage":"pubmed year-from must be <= year-to","messagePattern":"pubmed year-from must be <= year-to","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"warning","filePath":"clis/pubmed/author.js","lineNumber":45,"sourceCode":"        { name: 'year-to', type: 'int', help: 'Filter publication year to' },\n        { name: 'sort', default: 'date', choices: ['date', 'relevance'], help: 'Sort by date or relevance' },\n    ],\n    columns: LINK_COLUMNS,\n    func: async (args) => {\n        const name = requireText(args.name, 'author');\n        const limit = requireBoundedInt(args.limit, 20, 100);\n        const position = requireChoice(args.position, ['any', 'first', 'last'], 'position', 'any');\n        const sort = requireChoice(args.sort, ['date', 'relevance'], 'sort', 'date');\n        const yearFrom = requireYear(args['year-from'], 'year-from');\n        const yearTo = requireYear(args['year-to'], 'year-to');\n        const authorTag = position === 'first' ? '1au' : position === 'last' ? 'lastau' : 'au';\n        const terms = [`${name}[${authorTag}]`];\n        if (args.affiliation) terms.push(`${requireText(args.affiliation, 'affiliation')}[ad]`);\n        if (yearFrom || yearTo) {\n            const from = yearFrom || 1800;\n            const to = yearTo || new Date().getFullYear();\n            if (from > to) {\n                throw new ArgumentError('pubmed year-from must be <= year-to');\n            }\n            terms.push(`${from}:${to}[PDAT]`);\n        }\n        const esearch = await eutilsFetch('esearch', {\n            term: terms.join(' AND '),\n            retmax: limit,\n            usehistory: 'y',\n            sort: sort === 'date' ? 'pub_date' : '',\n        }, { label: 'pubmed author' });\n        const pmids = esearch?.esearchresult?.idlist;\n        if (!Array.isArray(pmids)) {\n            throw new CommandExecutionError('pubmed author did not return an id list', 'PubMed ESearch response shape may have changed.');\n        }\n        if (pmids.length === 0) {\n            throw new EmptyResultError('pubmed author', `No articles found for author \"${name}\".`);\n        }\n        return fetchSummaryRows(pmids, 'pubmed author summary');\n    },","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/pubmed/author.js#L27-L63","documentation":"ArgumentError thrown at clis/pubmed/author.js:45 during query construction when the user-supplied year-from is greater than year-to. The command builds a PDAT date-range term (`${from}:${to}[PDAT]`) for the PubMed esearch query and rejects impossible ranges before any network call is made. This is pure client-side input validation.","triggerScenarios":"Calling `pubmed author <name> --year-from 2023 --year-to 2020` (or equivalent args object {yearFrom: 2023, yearTo: 2020}); defaults fill missing values (from=1800, to=current year) so an explicitly bad pair is required to trigger it.","commonSituations":"Swapping the two flags by mistake; scripting calls where yearFrom/yearTo are computed dynamically and inverted (e.g. relative year offsets); copy-paste from an example with reversed order; user misunderstanding that year-from is the earlier bound.","solutions":["Swap the values so year-from <= year-to before invoking the command","Validate the two year args at the CLI/app level before calling pubmed author","If computed dynamically, clamp or sort from/to before passing them","Check flag names/order in your script — you may have assigned year-to to year-from"],"exampleFix":"// before\nconst args = { 'year-from': '2023', 'year-to': '2020' };\nawait runCli('pubmed author \"Jane Doe\"', args);\n// after\nconst args = { 'year-from': '2020', 'year-to': '2023' };\nif (Number(args['year-from']) > Number(args['year-to'])) {\n    throw new Error('year-from must be <= year-to');\n}\nawait runCli('pubmed author \"Jane Doe\"', args);","handlingStrategy":"validation","validationCode":"// guard the year range before invoking the command\nconst from = Number(opts['year-from']);\nconst to = Number(opts['year-to']);\nif (from > to) {\n  [opts['year-from'], opts['year-to']] = [opts['year-to'], opts['year-from']]; // or reject\n}","typeGuard":"function isValidYearRange(from, to) {\n  return Number.isInteger(from) && Number.isInteger(to) && from <= to;\n}","tryCatchPattern":"try {\n  const rows = await runCli('pubmed author', authorArgs);\n} catch (e) {\n  if (e instanceof ArgumentError || /year-from must be <= year-to/.test(e.message)) {\n    console.error('Bad year range: swap year-from and year-to and retry.');\n    process.exitCode = 2;\n    return;\n  }\n  throw e;\n}","preventionTips":["Validate/sort yearFrom/yearTo in your wrapper before calling the CLI","Remember defaults: from falls back to 1800, to to the current year — only explicit pairs can trigger this","Use CLI flag parsing rather than string interpolation to avoid swapped flags","Write a unit test for inverted ranges"],"tags":["cli","validation","argument-error","pubmed"],"backgroundTag":"invalid-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}