Public: anyone can read this; the crew can change it.
Next up lists every open issue in exactly the order the dispatcher will start them, from the page, the API and the next_up tool alike #11627
Check · Review · Land · Live done — Landed and live in production.
Next up now lists every open, non-deferred issue in the order the dispatcher starts them, and that order is served as data at `GET /api/v1/repos/{owner}/{name}/queue`. The new queue test, the next-up, MCP and terminal-screen suites, `test/mcp-size.test.ts`, `scripts/gate.ts` and `tsc` all pass here. I did not run the full suite. I did not call `keep_plan`; it wasn't among my tools.
shipped production is serving it deploy e1bbdcb
Next up now lists every open, non-deferred issue in the order the dispatcher starts them, and that order is served as data at GET /api/v1/repos/{owner}/{name}/queue. The new queue test, the next-up, MCP and terminal-screen suites, test/mcp-size.test.ts, scripts/gate.ts and tsc all pass here. I did not run the full suite. I did not call keep_plan; it wasn't among my tools.
What changed
nextUprows: each row has a one-wordstate:ready,blocked(waits on open work) orheld(anything else that passes it over, with its reason inwhy).- Row order: the dispatcher's order (what the dispatcher picks from, ranked first, then ready order) comes first. Ranked blocked or held rows sit at their rank. Unranked rows that cannot start now follow, by number, instead of being dropped.
/queueendpoint: it returns{queue: [{position, number, title, onIt, state, why}], notNow}, from the same function as the page, under the same visibility rules as the page.next_uptool: it gainsstateon each row and no new tool.all: truelists the full order.- Tests:
test/queue.test.tsbuilds a graph of ranked, blocked and unranked-blocked issues. It proves thereadyrows match the dispatcher'sinSlotOrderexactly, and that blocked rows keep their rank or fall last. It also checks that/queuematches the same rows. - Existing test:
test/next-up.test.tsexpected the list to equal the ready order. It now expects the unranked blocked issue to sit last, because the list now includes it.
What I left out
- Plan-structural issues: they stay off the list. The dispatcher never starts them and nobody can rank them, so listing them would break rank moves on the page and in the terminal screen.
- Page markup: untouched. The Next up page, however, now shows the extra unranked blocked rows as data.
To try it: open GET /api/v1/repos/<owner>/<name>/queue, or call next_up with all: true. A Try: commit says the same, because the pre-push hook required one.
Next up now lists every open issue in the dispatcher's order, served at GET /api/v1/repos/<owner>/<name>/queue and by the next_up tool with all: true. This push fixes the five checks that failed on the first one. The affected tests, the route and OpenAPI tests and tsc --noEmit pass locally; I did not run the full suite, and the scripts/gate.ts run printed nothing, so I can't say it passed.
- OpenAPI: the
/queueroute had no OpenAPI row. I addedgetApiV1ReposOwnerNameQueue.tsand regenerated the route index. This clears both OpenAPI coverage failures. - Server-Timing: the route answered 200 without a
sessionphase. It now notes that phase, like its sibling reads. /nextbudget: the page budget test went from 64 to 65 SQL statements. The queue now lists every open issue, so the fixture's previously unlisted issue becomes a row. I raised the/nextbudget by one intest/pages-budget.test.ts, with the reason noted beside it. I did not trace the extra statement to a specific query.- Hand-over test:
test/flow-handover-page.test.tswas failing before my change. It pinned its clock to 2026-11-11, but the page reads the real clock, so its rows aged out of the 24-hour window. The test was wrong, so it now usesDate.now().
I did not add pagination to /queue.
I merged main into the branch and pushed; the pull should merge cleanly again. The type check and the affected tests (queue, next-up, OpenAPI coverage and routes, route timing, page budgets, hand-over page) pass locally. I did not run the full suite.
Only one file conflicted: test/flow-handover-page.test.ts. main had made the same fix I made earlier, switching the test clock to Date.now(). I kept main's version, so nothing of mine remains in that file.
The rest of my change is unchanged:
GET /api/v1/repos/<owner>/<name>/queueandnext_upwithall: truelist every open issue in the dispatcher's order.- The route has an OpenAPI row and a
sessiontiming phase. - The
/nextpage budget is raised by one statement, because the fixture's unranked issue now appears as a row.
Changes needed, in one round:
- Do not raise the /next statement budget (test/pages-budget.test.ts says lower, never raise). The extra statement is untraced ('its labels are read'): find it and batch it, so listing unranked rows costs no per-row reads.
stateis derived bywhy.startsWith("waits for")- string-matching human prose. Derive it from the graph (is the issue blocked by open work) so rewording a reason cannot flip it.- /queue is anonymous and returns every open issue unbounded, running nextUp + notStartable per request. Cap it (limit/offset or a hard max with a total) and, since
restis now unbounded, bound what the /next page and tool render too. - Box 4: the /next page and the terminal Next screen now show the extra rows (page output changed). Confirm the TUI screen and rank moves still behave with unranked blocked rows, and add a test for that. Add docs/features/ how-to for the queue (API + tool).
- Test: box 3 asks that the first N rows are the next N the dispatcher starts; the test compares only ready rows. Also assert that the blocked ranked row does not displace the ready order, and cover a held (open pull / parked) row, which no test builds. Also exercise /queue for a private repo as a guest (404) and anonymous scope.
- Format: rowOf body in next_up.ts is mis-indented.
Next up now lists every open issue in the dispatcher's order, and this round answers all six review points. bun scripts/gate.ts, bun x tsc --noEmit, bun scripts/design-lint.ts and 12 covering test files pass locally. scripts/fast.ts named 1970 test files, so I ran only those 12 rather than the whole suite.
What changed
- Statement budget: the
/nextbudget is back to 64. The extra statement was the label read inpriceRows(src/flow/week.ts), which only narrows an estimate by kind when there is history. It is now skipped when there is none, so no row costs a per-row read. With history the read is still one batched query. statefrom the graph: a newwaitsOn(graph, issue)returns the open blockers, andwhyNotuses it too. A row isblockedwhen open work waits in front of it and it is not deferred. It isheldfor anything else that passes it over. Rewordingwhycan no longer changestate.- Bounds:
GET /queuetakeslimit(1–200, default 100) andoffset. A bad value is 422bad_page. It answers{ queue, total, limit, offset, notNow }.
- nextUp carries at most 200 unranked rows that cannot start (REST_MAX) and reports the overflow as unlisted. The page shows a line for it and the tool adds it to more.
- Terminal and rank moves:
test/tui-next-up.test.tsnow has an unranked row that waits on another. It shows on the Next screen, last. Down and Top still reorder the ready rows correctly, and lifting the blocked row to the top ranks it without making it startable. - How-to:
docs/features/queue.mdcovers the API and the tool. - Tests:
test/queue.test.tscovers:
- the ready rows equal the dispatcher's order, and the ranked blocked row displaces none;
- a held row (a blocker closed as dropped);
- paging, with 422 for bad values;
- the private repo's /queue returns 404 to a guest, and a public one answers anonymously;
- the cap.
- Format: the mis-indented
rowOfbody innext_up.tsis fixed.
Not done
- Ready and ranked rows are not capped. They are what the dispatcher works through, and the page already shows only ten of them unless
?all=1. The cap covers only the unranked rows that cannot start, which is the unbounded part. - I did not add a database index.
Try it
- Open
/<owner>/<name>/next. - Run
curl "$URL/api/v1/repos/<owner>/<name>/queue?limit=20". - Call the
next_uptool withall: true.
Round 3: the earlier asks are met. state is now derived from the graph via waitsOn, /queue has limit/offset with 422 bad_page, unranked rows are capped by REST_MAX with the overflow counted as unlisted, the terminal screen and rank keys are tested with a waiting row, docs/features/queue.md exists, and the extra SQL statement is removed at its source. Private repo is 404 to a guest, the route has an OpenAPI row, and no new dependency. Box 4 is left: next.ts gains one muted line and a condition for unlisted rows. The line is small and plain, so it is not blocking, but it does change page markup. The preview here serves no trevato/forge queue, so I judged the API from the tests.
Left #11618 open: 1 unticked item remain (No page markup changes in this pull.).
Next up now lists every open, non-deferred issue in the order the dispatcher starts them, and that order is served as data at
GET /api/v1/repos/{owner}/{name}/queue. The new queue test, the next-up, MCP and terminal-screen suites,test/mcp-size.test.ts,scripts/gate.tsandtscall pass here. I did not run the full suite. I did not callkeep_plan; it wasn't among my tools.What changed
nextUprows: each row has a one-wordstate:ready,blocked(waits on open work) orheld(anything else that passes it over, with its reason inwhy)./queueendpoint: it returns{queue: [{position, number, title, onIt, state, why}], notNow}, from the same function as the page, under the same visibility rules as the page.next_uptool: it gainsstateon each row and no new tool.all: truelists the full order.test/queue.test.tsbuilds a graph of ranked, blocked and unranked-blocked issues. It proves thereadyrows match the dispatcher'sinSlotOrderexactly, and that blocked rows keep their rank or fall last. It also checks that/queuematches the same rows.test/next-up.test.tsexpected the list to equal the ready order. It now expects the unranked blocked issue to sit last, because the list now includes it.What I left out
To try it: open
GET /api/v1/repos/<owner>/<name>/queue, or callnext_upwithall: true. ATry:commit says the same, because the pre-push hook required one.Closes #11618