HOMELAB-2682: fix(plane): plane_list_comments default limit, since-filter, paging, cut note #101
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/HOMELAB-2682-plane-comments-limit"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The incident (El Director, verbatim)
Root cause is not a missing
slice(): Plane'sGET …/work-items/<id>/comments/defaults to
per_page=1000, so a tool that names no page size receives thewhole thread. Measured against the deployed v1.3.1 while writing this:
per_page→ the entire thread (total_count=153, 557 721 bytes, one body);per_page=1000→ accepted;per_page=1001→400 {"detail":"Invalid per_page value. Cannot exceed 1000."};per_page+ acursorof the form<per_page>:<page>:<dir>(feednext_cursorback → the next OLDER page).So the fix bounds the read, not just the printed output.
The change
plane_list_commentsnow owns its window (plane/api.ts:listCommentPage,describeCommentPage;plane/index.ts: the tool):DEFAULT_COMMENT_LIMIT = 50) — well inside the failure case (250+) and inside Plane's own 1000-row page cap. Stated in the tool description andpromptGuidelinesso a future session knows.per_page = offset + limit→ one HTTP request, ~a few KB instead of 1.2 MB.limitlimit: 1000returns the whole thread on purpose.offsetoffset: 50is the next older page.details.nextOffsetis always the offset that continues.sincesinceis returned; one stamped 1 ms earlier is not). Invalid timestamps are refused, not guessed.created_atdesc,iddesc as tie-break), which is what the server already returned and what "the newest N" means. A page is a contiguous slice of one total ordering, so page N+1 continues page N with no gap and no overlap; "the last 10 comments" islimit: 10with no client-side sorting.showing comments 1-50 of 263 (newest first, offset 0); 213 older comment(s) not shown — pass offset=50 to page further back. Middle pages name both sides. An offset past the end says so instead of "no comments".A
sincefilter, or anoffsetdeeper than Plane's page cap, walks pages viaper_page+cursor; that walk is bounded (10 pages × 100 comments) andreports
WORTH KNOWING: the read stopped at 10 pages … so N is a floorratherthan presenting a bound as a total.
Internal reads are unchanged: the HOMELAB-1679 closure boundaries and the
sidebar still read every comment through the raw
listComments(the windowbelongs to the agent-facing tool, where the context budget is). The new query
params are optional on
listComments, so no other caller changes behaviour.Docs:
docs/plane.mdgains a "Reading comments: the default window"section and a gotcha ("
plane_list_commentscut the list — say so when yousummarise").
Tests
plane/tests/comments-limit.test.ts(21 tests) + a faithful page model inplane/tests/fake.ts(its comments route now honoursper_page/cursortheway the real API does, including the 1000-row default — so a tool that
forgets to bound its read sees the whole thread in tests exactly as in
production; one test asserts that an unbounded read of the same fixture really
is the whole 263-comment body).
Covered: default truncation + cut note · the request carrying
per_page=50·explicit
limit(small, and 1000 with no cut note) ·sinceinclusive boundary(exact stamp in, −1 ms out) ·
sincepaging over the filtered set · emptysincewindow vs empty ticket · invalidsince/limitrefusals · six-pageoffset walk over 263 comments with no gap/overlap, ending at the oldest comment
· middle-page note naming both sides · offset-past-end · newest-first ordering ·
walk-bound honesty (
incomplete, "is a floor") ·listCommentsunchanged forinternal callers.
Verification
plane/:npx tsc --noEmitclean;npx vitest run→ 4 files, 71 tests, all passing (21 new).bash forgejo/scripts/test-in-deployed-shape.sh plane): result of the run recorded in a comment below before merge.Nothing merged by this worker — one normal review seat follows.
Verification (worker report, head
36ebd81)Suites
plane:npx tsc --noEmit && npx vitest runtests/comments-limit.test.ts)bash forgejo/scripts/test-in-deployed-shape.sh planecommit/36ebd81…/statusCI / planeandCI / pr-title)Live proof against the deployed Plane (read-only; HOMELAB-2195, 153 comments):
The
sincecase is the inclusive-boundary proof on real data: the commentstamped exactly
sinceis in the result. The measured default-versus-unboundedgap (157 KB vs 557 KB for the same ticket, and 1.2 MB for the reported epic) is
the incident closed at the request, not only at the output.
Not merged by this worker: one normal (non-gate) review seat follows, then a
landing worker merges on green CI + a clean exact-head verdict.
Verdict of record — ld-reviewer — head
36ebd81999: APPROVEmodel: deepseek-flash
session: ld-reviewer-pr101-36ebd81
What I verified (independent seat — El Director's seven points)
planeExtension→execute→listCommentPage→normaliseLimit(undefined)=DEFAULT_COMMENT_LIMIT): a 263-comment thread returnsc262 … c213,matched=263,truncated=true, and one HTTP request carryingper_page=50. A small thread (4) returns all 4 with no cut wording.limit:10is the ten newest, newest-first,nextOffset=10; the header line is machine-followable without client sorting. Ordering is stated in the tool description, inpromptGuidelines, and indocs/plane.md.sinceboundary. Inclusive: a comment stamped exactlysinceis returned, one stampedsince+1msis not (verified with a synthetic thread and, below, against the live API). An unparseable stamp is refused (since must be an ISO-8601 timestamp), not ignored.details.nextOffsetwalks 263 comments in 6 pages (50×5 + 13) with 263 unique ids, no gap, no overlap, ending at the oldest comment; offset past the end says "past the end … use offset 0..262" instead of "no comments".213 older comment(s) not shown — pass offset=50) and on an explicit limit (256 … pass offset=7), names both sides on a middle page, and stays silent when nothing was withheld. Empty ticket vs emptysincewindow are distinguishable.git diff origin/main...36ebd81hunks are confined toplane/api.ts(comments section only —sortCommentsNewestFirst/timestamppre-existed unchanged), the one tool inplane/index.ts, its tests, the fake, and docs. I asserted host-side thatplane_list_commentsis the only tool now takingoffset/since. The internal rawlistCommentsreads (HOMELAB-1679 closure boundaries, sidebar) are untouched and still send noper_page.bash forgejo/scripts/test-in-deployed-shape.sh plane→ typecheck ok, 4/4 files loaded, 71/71 tests passed (vitest 2.1.9, pinned tree). Plus my own 17-assertion probe written from scratch (my own fetch model ofGET …/comments/— not the PR'sfake.ts): 17/17 green, covering all of the above.per_page=3→next_cursor=3:1:0, feeding it back returns three OLDER comments with zero id overlap;per_page=1001→400 "Cannot exceed 1000"; noper_page→ 153 rows / 557 721 bytes in one body;total_count=153even whenper_page=3, so the tool'smatchedis the true total. The fix's stated root cause and its mechanism both reproduce.Blockers
None.
Concerns
{offset:990, limit:50}returns 10 comments withtruncated:falseandnextOffset:null, and the header reads "showing comments 991-1000 of 1000" before appending the WORTH KNOWING floor clause.incomplete:trueis set, so nothing is hidden from a careful reader, but a caller that pages bynextOffsetstops there believing it saw the thread, and it got 10 of the 50 it asked for without being told. Non-blocking (the default and shallow paths are exact,sinceis the documented escape hatch, and this is a bound, not a bug). Suggested follow-up: derivetruncated/nextOffsetfromincomplete, and say "served N of the M you asked for because the read is bounded".docs/plane.mdand theplane/api.tsheader say ordering breaks ties oniddescending;sortCommentsNewestFirstsorts oncreated_atonly (pre-existing, unchanged by this PR). Ties therefore keep the server's order — fine in practice, but either add the tie-break or drop the claim.Reviewed only; I did not edit, commit or push anything.