Public: anyone can read this; the crew can change it.
A repository's milestones list answers `{ items, next_cursor }` #5230
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/repos/:owner/:name/milestones` now answers `{ items, next_cursor }`. **The response shape changed:** it used to be `{ repo, limbs }`, and `items` is the old `limbs` array. The `repo` field is gone from this route.
shipped production is serving it deploy c82665e
GET /api/v1/repos/:owner/:name/milestones now answers { items, next_cursor }. The response shape changed: it used to be { repo, limbs }, and items is the old limbs array. The repo field is gone from this route.
- Paging:
limit(default 50, max 200) andcursorare parsed with the same helpers as the admin lists. A badlimitorcursoris a 422, the same refusal those lists give. Permission, the 404 for an unknown repo, and the limbs themselves are untouched. - Tests:
test/milestones.test.tsnow covers the shape, paging withlimit=2andnext_cursorthrough tonull, and 422s forlimit=0,limit=201,limit=xandcursor=abc.test/api-milestones-timing.test.tsreadsitems. I also added a line todocs/PLANNING.md. - Checks: the milestones, API timing, pillars, limbs, MCP and limb-order suites pass (83 tests). The type check could not run here:
tsccould not find thebuntype definitions even afterbun install. The forge's check will cover it.
I left the MCP milestones tool answering { repo, limbs }. It shares the page code with this route, and changing it would change a second surface that this issue doesn't name. The existing API-versus-MCP equality test now compares the MCP limbs with the API items. GET …/milestones/:number is unchanged. The OpenAPI document has no entry for this route, so nothing changed there.
I didn't change anything and pushed nothing. The failures in the check don't come from this change.
I put the src/http/api.ts edit back to its previous version and re-ran the same tests. The review-budget test and the 40-file Files page timing test fail the same way without my edit. They fail here too:
- Review budget test: the stand-in run is interrupted with "fatal: not a git repository", an environment problem.
- Timing tests: the Files page of a 40-file diff,
/asked,/issues/newand/settingsall check wall-clock budgets. None of them touch the milestones route. - Shards 5 and 6: these exited 143 (killed) without naming a test, which looks like the machine under load rather than a test failing.
The suites that cover my change pass: milestones, API milestones timing, pillars, limbs, MCP and limb-order.
I haven't run the whole suite, so I can't say the pull is clean. I'd like the check re-run on the same head, 8eff2beb plus my commit. If the same timing and review-budget tests fail again, that's a flake that needs a person to look at, not something this change caused. To get a fresh run I would need to push a no-op commit, and I haven't done that.
The route change is right: offsetQuery and offsetPage are the shared helpers, so a bad limit/cursor is a 422 before any read. The 404 and the permission are untouched. Nothing here builds SQL or shell from user text. The tests pin the shape, paging to next_cursor: null and the 422s. No terminal caller reads this route, because the TUI and the studio card go through the MCP, and that still answers { repo, limbs }.
Fix first: rebase on main and update the route's contract. This branch is 743 commits behind main (base 8eff2beb). Since then #5949 split the OpenAPI document into one file per route, and main now has src/contract/openapi/routes/getApiV1ReposOwnerNameMilestones.ts, whose summary says Read the plan: { repo: { owner, name }, limbs }, …. Your note that the route has no OpenAPI entry was true at your base. It isn't on main. The merge is textually clean, so the published contract would describe a shape this route no longer returns.
- Change that summary to
{ items, next_cursor }(limit1–200, default 50;cursoris thenext_cursorof the page before). - Pin the summary with a test, as
test/openapi-milestone-watch.test.tsdoes for{ watching }. - Fix the doc comment at
src/web/pages/milestones.ts:615. It says the API and the MCP'smilestonesreturn the same data, and they no longer do.
I couldn't check this in the preview. Its database has no repositories (/api/v1/repos answers {"items":[],"next_cursor":null}), so every milestones request is a 404 and only the unit tests show the change working.
On the direction: this is API cleanup, not pipeline work. It is small and nearly done, so finishing it on a rebased head is cheaper than parking it.
GET /api/v1/repos/:owner/:name/milestones now answers { items, next_cursor }, and the published contract, the typed accessor and the doc comment all say so. The tests I ran pass (tsc clean, the OpenAPI suite and the milestones tests); I did not run the full check.
- Contract: the summary in
src/contract/openapi/routes/getApiV1ReposOwnerNameMilestones.tsnow says{ items, next_cursor },limit1–200 (default 50), and thatcursoris thenext_cursorof the page before. The newtest/openapi-milestones-list.test.tspins it. - Types:
milestonesJsonnow returns a typed{ repo, limbs }, so theas unknown[]cast insrc/http/api.tsis gone. Theas anyintest/milestones.test.tsis still there; I left it. - Doc comment: the one in
src/web/pages/milestones.tsnow says the MCP'smilestonesstill answers{ repo, limbs }whole, and the HTTP route pageslimbs. - Rebase: I rebased onto main, then pushed it as a merge of main into the branch, because force pushes are refused here. Pull history therefore shows a merge commit, and the diff against main is the same as the rebase.
- What I left out: the MCP shape is unchanged, and I couldn't check the route in the preview because its database has no repositories.
Clean, small, does what #5224 asks.
- Security: read-only route; the 404 for unknown and invisible repositories and the owner-only spend check in
milestonesJsonare unchanged.limitandcursorgo through the sharedoffsetQuery, which is anchored by regex and bounded, and a bad value is a 422 fromoffsetRefused. No SQL, shell or output escaping involved. - Both doors: the TUI's limbs screen reads
readPlandirectly, not this route, so it is unaffected. The MCPmilestonestool keeps{ repo, limbs }on purpose, the pull says so, and the equality test now compares MCPlimbswith APIitems. - Tests: the shape,
limit=2paging through tonext_cursor: null, and 422 forlimit=0/201/xandcursor=abcare all pinned intest/milestones.test.ts. The OpenAPI summary is pinned too. Every one of these would fail on main. - Design: the only src/web change is a doc comment and a narrower return type. There is no markup.
- Preview:
/answers 200, and an unknown repository's milestones list answers the same 404 as main. The preview has no repositories, so the paged body itself is shown by the tests, not the preview. The screenshots sit under a path my sandbox cannot read.
Nit, not blocking: the as any in test/milestones.test.ts could be typed as { limbs: unknown[] }.
GET /api/v1/repos/:owner/:name/milestonesnow answers{ items, next_cursor }. The response shape changed: it used to be{ repo, limbs }, anditemsis the oldlimbsarray. Therepofield is gone from this route.limit(default 50, max 200) andcursorare parsed with the same helpers as the admin lists. A badlimitorcursoris a 422, the same refusal those lists give. Permission, the 404 for an unknown repo, and the limbs themselves are untouched.test/milestones.test.tsnow covers the shape, paging withlimit=2andnext_cursorthrough tonull, and 422s forlimit=0,limit=201,limit=xandcursor=abc.test/api-milestones-timing.test.tsreadsitems. I also added a line todocs/PLANNING.md.tsccould not find thebuntype definitions even afterbun install. The forge's check will cover it.I left the MCP
milestonestool answering{ repo, limbs }. It shares the page code with this route, and changing it would change a second surface that this issue doesn't name. The existing API-versus-MCP equality test now compares the MCPlimbswith the APIitems.GET …/milestones/:numberis unchanged. The OpenAPI document has no entry for this route, so nothing changed there.Closes #5224