Skip to content

[Detail Bug] CLI: bugs list --status ... pagination drops bugs when multiple statuses are requested #300

@detail-app

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_5f375fe3-a706-4e9a-a6f7-800f2439b3f6/bugs/bug_f56bccb4-2005-4841-84fd-6a6311e547b4

Introduced in #298 on May 16, 2026

Summary

  • Context: The bugs list command supports multiple --status values, and fetch_page_multi_status handles pagination.
  • Bug: Multi-status pagination silently drops bugs. The shared offset mechanism skips bugs that were truncated on previous pages.
  • Actual vs. expected: Actual: pages after the first can be empty or missing items even though total indicates more results; some bugs become permanently unreachable via pagination. Expected: paginating through pages should eventually return every bug matching any requested status.
  • Impact: Users cannot access all bugs through pagination, regardless of status ordering.

Code with Bug

// src/commands/bugs.rs:418-440
async fn fetch_page_multi_status(
    client: &ApiClient,
    repo_id: &RepoId,
    statuses: &[BugReviewState],
    limit: u32,
    page: u32,
    scan_id: Option<&ListPublicBugsWorkflowRequestId>,
) -> Result<(Vec<Bug>, usize)> {
    let offset = page_to_offset(page, limit);  // <-- BUG 🔴 same offset applied to every status
    let mut combined = Vec::new();
    for status in dedupe_statuses(statuses) {
        let response = client.list_bugs(repo_id, status, limit, offset, scan_id).await?;
        combined.extend(response.bugs);
    }
    combined.truncate(limit);  // <-- BUG 🔴 truncates after concatenation, losing items permanently
    Ok((combined, total))
}

Explanation

The function uses a single offset = (page-1)*limit for each status query, then concatenates all results and truncates to limit.

If page 1 produces more than limit combined results, items from later statuses are truncated away. On page 2, those truncated items cannot be recovered because the API calls for each status start at offset=limit, skipping the first limit items of that status—even though many of them were never shown. This creates a blind spot where truncated items are skipped forever.

Concrete example (50 pending + 40 resolved, limit=50): page 1 returns 50 pending and truncates away all 40 resolved; page 2 queries resolved with offset=50 (past the end) so it returns nothing. Those resolved bugs are never shown. Reordering statuses only changes which status loses items; it does not fix the reachability issue.

Recommended Fix

Apply offset after combining rather than per-status: over-fetch up to offset+limit for each status starting at offset 0, then drop offset items from the combined list and truncate to limit.

async fn fetch_page_multi_status(
    client: &ApiClient,
    repo_id: &RepoId,
    statuses: &[BugReviewState],
    limit: u32,
    page: u32,
    scan_id: Option<&ListPublicBugsWorkflowRequestId>,
) -> Result<(Vec<Bug>, usize)> {
    let mut combined = Vec::new();
    let mut total: usize = 0;

    let fetch_limit = page_to_offset(page, limit) + limit;
    for status in dedupe_statuses(statuses) {
        let response = client.list_bugs(repo_id, status, fetch_limit, 0, scan_id).await?;
        total += usize::try_from(response.total.max(0)).unwrap_or(0);
        combined.extend(response.bugs);
    }

    let offset = page_to_offset(page, limit);
    combined.drain(..offset.min(combined.len()));
    combined.truncate(limit);

    Ok((combined, total))
}

History

This bug was introduced in commit 5d7956b. The change added fetch_page_multi_status to avoid fetching all pages client-side for multi-status queries, but the shared-offset + concatenate-then-truncate approach makes some results unreachable on page 2+.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions