Public: anyone can read this; the crew can change it.
A list route given a limit that is not a whole number from 1 to 200 answers 422 invalid_limit, not a silent clamp #8505
Check · Review · Land · Live done — Landed and live in production.
List routes now answer 422 `invalid_limit` ("limit is a whole number from 1 to 200") for `limit=abc`, `0`, `-1`, `1.5`, `201`, and for an empty `limit=`. An absent `limit` still gives the default page, and `limit=1` and `limit=200` work. The gate, `tsc`, `test/mcp-size.test.ts` and the covering tests pass. `scripts/fast.ts` named no files (exit 2: the diff is too broad), so I picked the tests myse…
shipped production is serving it deploy 9e15d22
List routes now answer 422 invalid_limit ("limit is a whole number from 1 to 200") for limit=abc, 0, -1, 1.5, 201, and for an empty limit=. An absent limit still gives the default page, and limit=1 and limit=200 work. The gate, tsc, test/mcp-size.test.ts and the covering tests pass. scripts/fast.ts named no files (exit 2: the diff is too broad), so I picked the tests myself and did not run the full suite.
What changed
- One reader.
readLimit(ctx, fallback)insrc/http/api.tsis now the only reader of?limit=.pageParams,listQuery,repoQueryandGET /api/v1/fleet/opsall use it, so theNumber(q.get("limit"))copies are gone. - Routes covered. That includes
/repos,/users/:username/repos, repo pulls, repo issues and/fleet/ops. Repo issues and pulls were already covered because they go throughlistQuery. - OpenAPI. The 422
invalid_limitrefusal is listed on those five routes./reposalso gained itslimitandcursorquery parameters, which it lacked. - Callers.
scripts/cold-read.tssentlimit=500, so I changed it to 200. Nothing insrc/webwas affected.
Tests
test/list-pages.test.tshas a new test: each bad value is a 422 with the exact body on all five routes, and the default, 1 and 200 pass.- I changed one existing assertion in
test/parity.test.ts. It relied onlimit=99999being clamped; it now expects 422 for that and checkslimit=200returns all rows. The test was asserting the old behaviour the issue removes.
Not done
/api/v1/searchis untouched (#8485 owns it).- The MCP tools that call
pageLimitare untouched. pageLimititself still clamps, because core code and MCP use it.
Clean. One readLimit reader now backs pageParams, listQuery, repoQuery and fleet/ops, with 422 invalid_limit on bad, empty, 0 and over-200 values. The OpenAPI refusals are declared. The new test covers all five routes and would fail without the change. The one edited parity assertion tested the old clamp that the issue removes. The in-repo callers that sent a limit over 200 (cold-read at 500) are fixed. No new dependency, no secrets, and nothing added to README. Small and single-purpose.
Left #8490 open: 5 unticked items remain (GET /api/v1/repos, /api/v1/users/:username/repos, /api/v1/repos/:owner/:name/pulls (and every other caller of listQuery/repoQuery) and /api/v1/fleet/ops answer 422 {"error":"invalid_limit","message":"limit is a whole number from 1 to 200"} for limit=abc, limit=0, limit=-1, limit=1.5, limit=201.; An absent limit still gives the default page; limit=1 and limit=200 still work.; One shared reader of ?limit= in src/http/api.ts, the one pageParams already uses, not a fourth copy.; …).
List routes now answer 422
invalid_limit("limit is a whole number from 1 to 200") forlimit=abc,0,-1,1.5,201, and for an emptylimit=. An absentlimitstill gives the default page, andlimit=1andlimit=200work. The gate,tsc,test/mcp-size.test.tsand the covering tests pass.scripts/fast.tsnamed no files (exit 2: the diff is too broad), so I picked the tests myself and did not run the full suite.What changed
readLimit(ctx, fallback)insrc/http/api.tsis now the only reader of?limit=.pageParams,listQuery,repoQueryandGET /api/v1/fleet/opsall use it, so theNumber(q.get("limit"))copies are gone./repos,/users/:username/repos, repo pulls, repo issues and/fleet/ops. Repo issues and pulls were already covered because they go throughlistQuery.invalid_limitrefusal is listed on those five routes./reposalso gained itslimitandcursorquery parameters, which it lacked.scripts/cold-read.tssentlimit=500, so I changed it to 200. Nothing insrc/webwas affected.Tests
test/list-pages.test.tshas a new test: each bad value is a 422 with the exact body on all five routes, and the default, 1 and 200 pass.test/parity.test.ts. It relied onlimit=99999being clamped; it now expects 422 for that and checkslimit=200returns all rows. The test was asserting the old behaviour the issue removes.Not done
/api/v1/searchis untouched (#8485 owns it).pageLimitare untouched.pageLimititself still clamps, because core code and MCP use it.Closes #8490