OpenHands / OpenHands/enterprise
[Bug]: V1 git pagination drops one item at every page boundary; search endpoints advertise next pages that duplicate or 400
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 4
- Forks
- 2
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 101
Description
Summary
The V1 git endpoints in openhands/app_server/git/git_router.py detect "is there a next page?" by requesting per_page = limit + 1 from the provider and checking whether more than limit items came back. That trick is only valid for offset-based pagination. The provider services implement page-number pagination (page/per_page are forwarded to the Git provider's API), so requesting limit + 1 items per page shifts every page window by one item — and one item is silently lost at every page boundary.
Affected endpoints
1. GET /api/v1/git/repositories/search without query (user repo listing) — data loss
git_router.py lines 183–196: the router calls client.get_repositories(..., page=page, per_page=limit + 1, ...), truncates the extra item, and emits next_page_id = page + 1.
Example with limit=30 against GitHub (openhands/app_server/integrations/github/service/repos.py:92 forwards page/per_page directly to /user/repos):
- Page 1 →
?per_page=31&page=1→ repos 1–31, shows repos 1–30 - Page 2 →
?per_page=31&page=2→ repos 32–62
Repo 31 is never shown. The frontend repo picker (frontend/src/hooks/query/use-git-repositories.ts) infinite-scrolls via next_page_id, so users with many repositories simply cannot find every ~limit-th repository in the dropdown.
2. GET /api/v1/git/branches/search with empty query (branch listing) — same data loss
Same limit + 1 pattern (lines 263–274), so one branch disappears at every page boundary — even though ProviderHandler.get_branches() already returns PaginatedBranchesResponse.has_next_page, which the router ignores.
3. GET /api/v1/git/repositories/search with query — infinite duplicate pages
page is decoded from page_id but never passed to client.search_repositories() (lines 156–171), yet the response still advertises a next_page_id whenever more than limit results come back. Following it returns the same first page again, forever — in an infinite-scroll UI this renders as endlessly repeating search results.
4. GET /api/v1/git/branches/search with query — advertises a page that 400s
Branch search rejects page != 1 with HTTP 400 (the OpenHands/enterprise#22 TODO), but still returns a non-null next_page_id when more than limit matches exist — advertising a next page that is guaranteed to fail when requested.
The existing test suite encodes the data loss
tests/unit/app_server/test_git_router.py::TestSearchRepositories::test_pagination_works_across_pages mocks provider data faithfully to page-number semantics (limit=2, so per_page=3; page 1 → repos 1,2,3; page 2 → repos 4,5,6) and asserts page 1 shows repos 1,2 and page 2 shows repos 4,5 — i.e. the test asserts that repo 3 (and repo 6) are never shown to the user.
Proposed fix (minimal, router-only)
- Listing paths: request
per_page = limit(notlimit + 1).- Branches: derive
next_page_idfrom thehas_next_pageflag the providers already return. - Repos: emit
next_page_idwhen a full page came back (len(repos) >= limit) — worst case is one trailing empty page, never lost data.
- Branches: derive
- Search-with-query paths: stop advertising a
next_page_idthat cannot be honored (returnnull) until provider-level search pagination lands (#13883 tracks branch search; repository search needs the equivalent provider work). For consistency with branch search, reject an explicit non-firstpage_idon repository query search with 400 instead of silently returning duplicate data.
I have a PR ready for this and will link it here.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in openhands/app_server/git/git_router.py at the repository and branch search pagination paths, then inspect the provider pagination behavior and frontend/src/hooks/query/use-git-repositories.ts. Run tests/unit/app_server/test_git_router.py::TestSearchRepositories::test_pagination_works_across_pages and update coverage for listing and query searches. Done means page boundaries no longer drop items, duplicate results, or advertise failing pages.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, github, python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100