Problem
When --output-limit trims a page that a producer --limit already cut, next_cursor still points after the last row of the producer page, not after the last row actually delivered. An agent that follows the advertised cursor silently loses every row between the two limits. No error, no has_more signal for the gap.
Reproduced on @unbrained/pm-cli 2026.10.6 (and 2026.10.5) with 70 open items:
pm list --json --limit 50 --output-limit 30 --output-budget unbounded > p1.json
jq '.items|length, .items[-1].id' p1.json # 30, "demo-0007" (row index 29)
jq -r .next_cursor p1.json | base64 -d # {"after_id":"demo-0038","after_index":49}
pm list --json --limit 50 --after "$(jq -r .next_cursor p1.json)" --output-budget unbounded \
| jq '.items[0].id' # "demo-0043" (row index 50)
Rows 30–49 (demo-0012 … demo-0038) are never returned by any page. #1411 fixed the stale-replay case where --output-limit and --output-budget both cut rows. This simpler combination, a producer --limit plus an --output-limit trim, still advertises the producer cursor.
Why it matters
--output-limit is the token-saving control agents are told to use, and following next_cursor is the documented way to page. The combination drops data silently. That is worse than a refusal: an agent doing triage or a sweep ("close every stale item") believes it saw everything.
Expected
- When an output ceiling (
--output-limit or --output-budget) removes rows from a producer page, rebase next_cursor to the last delivered row (after_id = that row, after_index = the producer page's start index + delivered count − 1), exactly as the read-output budget path already does for its own truncation.
- Or, if the producer cursor is intentionally kept, refuse the trim with an explicit receipt that names the undelivered range and how to fetch it.
- A regression test that walks every page under
--limit N --output-limit M (M < N) and asserts the union of delivered ids equals the full result set, in order, without duplicates.
(Found while keeping the native Rust port, pm-rust, byte-identical to the published CLI: the port reproduces this exactly, so it is fixed upstream first and mirrored after.)
Problem
When
--output-limittrims a page that a producer--limitalready cut,next_cursorstill points after the last row of the producer page, not after the last row actually delivered. An agent that follows the advertised cursor silently loses every row between the two limits. No error, nohas_moresignal for the gap.Reproduced on
@unbrained/pm-cli2026.10.6 (and 2026.10.5) with 70 open items:Rows 30–49 (
demo-0012…demo-0038) are never returned by any page. #1411 fixed the stale-replay case where--output-limitand--output-budgetboth cut rows. This simpler combination, a producer--limitplus an--output-limittrim, still advertises the producer cursor.Why it matters
--output-limitis the token-saving control agents are told to use, and followingnext_cursoris the documented way to page. The combination drops data silently. That is worse than a refusal: an agent doing triage or a sweep ("close every stale item") believes it saw everything.Expected
--output-limitor--output-budget) removes rows from a producer page, rebasenext_cursorto the last delivered row (after_id= that row,after_index= the producer page's start index + delivered count − 1), exactly as the read-output budget path already does for its own truncation.--limit N --output-limit M(M < N) and asserts the union of delivered ids equals the full result set, in order, without duplicates.(Found while keeping the native Rust port, pm-rust, byte-identical to the published CLI: the port reproduces this exactly, so it is fixed upstream first and mirrored after.)