The bug is in how the start index is calculated. start = page * size treats page as 0-based, so page 2 starts at index 4 and returns [4, 5]. For a 1-based page, the start index needs to be (page - 1) * size.
--- a/utils/pagination.py
+++ b/utils/pagination.py
@@ -2,7 +2,7 @@ def paginate(items, page, size):
"""Return the items on a 1-based page."""
if size <= 0:
raise ValueError("size must be positive")
- start = page * size
+ start = (page - 1) * size
end = start + size
return items[start:end]
--- a/tests/test_pagination.py
+++ b/tests/test_pagination.py
@@ -3,2 +3,10 @@ from utils.pagination import paginate, page_count
def test_page_count():
assert page_count([1, 2, 3, 4, 5], 2) == 3
+
+
+def test_paginate_is_one_based():
+ items = [1, 2, 3, 4, 5]
+ assert paginate(items, page=1, size=2) == [1, 2]
+ assert paginate(items, page=2, size=2) == [3, 4]
+ assert paginate(items, page=3, size=2) == [5]
+ assert paginate(items, page=4, size=2) == []
The new test checks the case you reported (page 2), plus the first page, the partial last page, and a page past the end. I couldn't run it because the files aren't in this workspace. After you apply the diff, run pytest tests/test_pagination.py. The new test should fail on the old code and pass with the fix.
Something to consider: this diff only fixes the off-by-one. It still doesn't check that page is valid. Now that pages start at 1, page=0 gives start = -size, so it would return a slice taken from the end of the list instead of failing. If you want that rejected, add if page < 1: raise ValueError("page must be >= 1") next to the existing size check. I didn't include it because it changes behavior beyond the bug you reported.
Real run recorded with claude-code / claude-opus-5-5. Output is shown verbatim, unmodified.
What it does
A working rule for bug fixes and small behavior changes: change only the layer responsible, and prove the fix.
How it works
- Reproduces the failure first when that is cheap; otherwise gathers the strongest available evidence.
- Traces the symptom to the responsible mechanism and changes that layer only.
- Avoids cleanup, renaming and new abstractions outside the fix, and leaves unrelated behavior and your own changes alone.
- Adds only the regression proof the task needs, runs it plus the nearest affected checks, and stops once they pass.
Good for
Bug fixes in established codebases where reviewers want small, focused diffs.
Worth knowing
One of the general work patterns from the open-source Caveman project. The instructions themselves carry no Caveman branding and work in any project.
Pure instruction file: no scripts, no network access and no file writes. The package also contains LICENSE, NOTICE and agents/openai.yaml (display name and default prompt for Codex).