pipe-cd / pipe-cd/pipecd

ListDeployments returns unbounded filtered pages with an unstable cursor

Open
#7,074 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
1.4k
Forks
364
Avg merge
1d 4h
Merged PRs (30d)
84

Description

What happened:
WebAPI.ListDeployments can return more than one logical page of deployments when label filtering is used. The handler paginates in the datastore layer, applies label filtering in memory, and keeps appending matches from subsequent datastore pages until the scan stops. That means one response can grow beyond the requested PageSize while still returning only the last datastore cursor.

Summary

ListDeployments does not enforce a stable filtered page size after applying label filters.

Affected Area

  • pkg/app/server/grpcapi/web_api.go
  • WebAPI.ListDeployments

Steps to Reproduce

  1. Arrange multiple deploymentStore.List pages where several deployments match the requested labels.
  2. Call WebAPI.ListDeployments with those label filters and a PageSize smaller than the total number of matching deployments.
  3. Observe that the response accumulates matching deployments across datastore pages into a single Deployments array.
  4. Observe that the returned cursor is only the final datastore cursor rather than a cursor aligned to the filtered response boundary.

Actual Behaviour

One response can contain more than the intended logical page size when label filtering is applied, and the cursor does not describe a stable resume point for filtered pagination.

Technical Evidence

  • The current implementation filters the first datastore page in memory and, if fewer than PageSize items remain, continues fetching more datastore pages and appending every matching deployment.
  • The handler currently returns filtered without truncating it to PageSize and includes a TODO noting that ListDeployments can exceed the page size.
  • Relevant code: pkg/app/server/grpcapi/web_api.go around the ListDeployments label-filter loop.

Suspected Root Cause

Filtering happens after datastore pagination, but the filtered result set does not have its own page boundary or resume cursor.

Proposed Direction

  • Keep fetching datastore pages until the filtered response reaches the requested logical page size.
  • Stop at the exact filtered page boundary instead of returning every matched deployment gathered during the scan.
  • Return a cursor that resumes from the correct datastore position for the next filtered page.
  • Add regression tests that cover dense and sparse label matches across multiple datastore pages.

Acceptance Criteria

  • ListDeployments never returns more than the requested logical page size when label filters are used.
  • A follow-up request using the returned cursor yields the next filtered page without duplicates or gaps.
  • Regression tests cover multi-page filtered pagination.

What you expected to happen:
ListDeployments should honor a stable filtered page size and return a cursor that resumes exactly after the last item included in the filtered response.

How to reproduce it:

  1. Arrange or mock multiple deploymentStore.List pages containing deployments with matching labels.
  2. Call WebAPI.ListDeployments with label filters and a PageSize smaller than the total filtered match count.
  3. Inspect the response and verify that it currently returns more than one logical page of matches.

Environment:

  • piped version: Not applicable
  • control-plane version: master as of July 22, 2026
  • Others: Reproducible from the current pkg/app/server/grpcapi/web_api.go implementation and a focused unit test.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in pkg/app/server/grpcapi/web_api.go at WebAPI.ListDeployments and the label-filter loop, then run or inspect the focused unit tests for this handler. Add regression coverage for dense and sparse matches across datastore pages; done means filtered responses respect PageSize and follow-up requests resume without duplicates or gaps.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.