Skip to content

logs: keep paging in all-workflows mode when full batches filter to zero runs#39741

Merged
pelikhan merged 3 commits into
mainfrom
copilot/fix-logs-paginated-results
Jun 17, 2026
Merged

logs: keep paging in all-workflows mode when full batches filter to zero runs#39741
pelikhan merged 3 commits into
mainfrom
copilot/fix-logs-paginated-results

Conversation

Copilot AI commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

In all-workflows mode, gh aw logs could stop after a filtered-empty batch even when GitHub returned a full page of runs, causing silent truncation in high-CI repos (especially with skipped/cancelled-heavy agentic workflows). This change makes pagination termination depend on raw API exhaustion, not post-filter emptiness.

  • Pagination stop condition

    • Reworked the orchestrator loop to stop only when the raw page is exhausted (totalFetched < batchSize).
    • If a batch filters to zero runs but was fully fetched, iteration now continues instead of terminating.
  • Cursor progression from raw batch

    • Added propagation of the oldest raw run timestamp from listWorkflowRunsWithPagination back to the orchestrator.
    • Cursor advancement now prefers the raw-batch oldest timestamp, so pagination moves past skipped/cancelled-only windows even when filtered results are empty.
  • Focused pagination logic coverage

    • Added unit tests for:
      • stop-vs-continue decision based on raw fetched count
      • cursor selection preferring raw-batch oldest run
      • fallback behavior when only filtered runs are available
if len(runs) == 0 {
    if shouldStopPagination(totalFetched, batchSize) {
        break // true end-of-history
    }

    cursor, ok := selectPaginationCursorDate(nil, oldestFetchedCreatedAt)
    if !ok {
        break
    }

    beforeDate = cursor
    continue // full page filtered to zero; keep paging
}

Copilot AI and others added 2 commits June 17, 2026 06:39
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix logs pagination issue in all-workflows mode logs: keep paging in all-workflows mode when full batches filter to zero runs Jun 17, 2026
Copilot AI requested a review from pelikhan June 17, 2026 06:48
@pelikhan pelikhan marked this pull request as ready for review June 17, 2026 12:06
Copilot AI review requested due to automatic review settings June 17, 2026 12:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes gh aw logs pagination in all-workflows mode so iteration doesn’t terminate early when a fully-fetched raw page filters down to zero (e.g., skipped/cancelled-heavy windows). The orchestrator now continues paging based on raw API exhaustion and advances the cursor using the oldest run timestamp from the raw batch.

Changes:

  • Reworked pagination stop/continue logic to use totalFetched < batchSize rather than post-filter emptiness.
  • Propagated the oldest raw CreatedAt timestamp from listWorkflowRunsWithPagination to drive cursor advancement even when filtered batches are empty.
  • Added focused unit tests for the pagination helper logic; updated several compiled workflow .lock.yml schemas for pull_request_number.
Show a summary per file
File Description
pkg/cli/logs_orchestrator.go Adjusts the main logs paging loop to continue on filtered-empty full pages and advances cursor from raw batch oldest timestamp.
pkg/cli/logs_orchestrator_pagination_test.go Adds unit tests for the new pagination helper functions.
pkg/cli/logs_github_api.go Extends ListWorkflowRunsOptions and returns the oldest raw run timestamp to the orchestrator for pagination cursor progression.
.github/workflows/test-quality-sentinel.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/smoke-copilot.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/smoke-copilot-arm.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/smoke-copilot-aoai-entra.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/smoke-copilot-aoai-apikey.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/smoke-claude.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/security-review.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/refiner.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/pr-triage-agent.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/pr-nitpick-reviewer.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/pr-code-quality-reviewer.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/mattpocock-skills-reviewer.lock.yml Updates pull_request_number schema to issueOrPRNumber.
.github/workflows/grumpy-reviewer.lock.yml Updates pull_request_number schema to issueOrPRNumber.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 16/16 changed files
  • Comments generated: 2

Comment thread pkg/cli/logs_github_api.go
Comment thread pkg/cli/logs_orchestrator.go
@github-actions

Copy link
Copy Markdown
Contributor

@copilot review all comments and address the unresolved review feedback on the pagination cursor handling.
@copilot summarize the remaining blockers and the next step to get this PR ready for merge.

Generated by 👨‍🍳 PR Sous Chef ·

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ failed during design decision gate check.

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@pelikhan pelikhan merged commit 3d42e71 into main Jun 17, 2026
75 of 76 checks passed
@pelikhan pelikhan deleted the copilot/fix-logs-paginated-results branch June 17, 2026 12:24
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 100/100 — Excellent

Analyzed 5 test scenario(s) across 4 Go test functions (1 table-driven with 2 rows): 5 design, 0 implementation, 0 guideline violation(s).

📊 Metrics & Test Classification (5 scenarios analyzed)
Metric Value
New/modified tests analyzed 5 (4 functions; TestShouldStopPagination is table-driven with 2 rows)
✅ Design tests (behavioral contracts) 5 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 5 (100%)
Duplicate test clusters 0
Test inflation detected No (77 test lines / 77 production lines ≈ 1:1)
🚨 Coding-guideline violations 0
Test File Classification Issues Detected
TestShouldStopPagination (row: stop) pkg/cli/logs_orchestrator_pagination_test.go:10 ✅ Design
TestShouldStopPagination (row: continue) pkg/cli/logs_orchestrator_pagination_test.go:10 ✅ Design
TestSelectPaginationCursorDate pkg/cli/logs_orchestrator_pagination_test.go:41 ✅ Design
TestSelectPaginationCursorDateFallsBackToFilteredRuns pkg/cli/logs_orchestrator_pagination_test.go:55 ✅ Design
TestSelectPaginationCursorDateNoCursor pkg/cli/logs_orchestrator_pagination_test.go:70 ✅ Design

Go: 1 file (*_test.go); JavaScript: 0. Other languages detected but not scored.

Verdict

Check passed. 0% implementation tests (threshold: 30%). All five test scenarios verify observable behavioral contracts — boundary conditions for pagination stopping, cursor-date selection precedence (raw fetch preferred over filtered), fallback to filtered runs, and the no-cursor sentinel — each directly tied to the bug this PR fixes.

🧪 Test quality analysis by Test Quality Sentinel ·

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 100/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnose and /tdd — 5 observations, no blocking issues.

📋 Key Themes & Highlights

Key Themes

  • Missing orchestrator-level regression test: the two extracted helpers are well-tested in isolation, but the continue path that is the heart of the bug fix has no end-to-end test. A swap of continuebreak in the loop would pass all new tests.
  • Cursor stagnation not guarded: in repos with many same-second runs a full batch can repeatedly produce the same beforeDate, spinning to MaxIterations silently.
  • Out-param in options struct: OldestFetchedCreatedAt *time.Time mixes input and output concerns in ListWorkflowRunsOptions; an extra return value would be cleaner Go.
  • Undocumented ordering assumption: both selectPaginationCursorDate and the runs[totalFetched-1] assignment rely on descending-time sort order from the API without documenting that contract.

Positive Highlights

  • ✅ Root cause is correctly identified and addressed — stop condition now uses raw API exhaustion, not post-filter emptiness.
  • shouldStopPagination and selectPaginationCursorDate extracted as pure, testable helpers — good decomposition.
  • ✅ PR description is clear and even includes a representative code snippet; existing comment in the loop explains the old bug well.
  • ✅ Verbose logging on both the continue and fallback-break paths makes the new behaviour observable.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

if ok {
t.Fatalf("expected no cursor, got %s", cursor)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnose] The helpers are tested in isolation, but the orchestrator loop's continue path — the core of this bug fix — has no regression test. If someone swaps continue for break in the if len(runs) == 0 block, all these tests still pass.

💡 Suggested approach

Add a test that exercises the orchestrator with a mock listWorkflowRunsWithPagination that returns:

  1. A full batch (totalFetched == batchSize) with zero filtered runs
  2. A partial batch that terminates pagination

Assert the API is called at least twice and beforeDate correctly advances. This directly documents and protects the fix.

totalFetched: 250,
batchSize: 250,
want: false,
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] TestShouldStopPagination is missing the totalFetched == 0 case. It correctly returns true (stop) via the formula, but an explicit test documents the intent and would catch a future refactor that accidentally changes the semantics for empty batches.

💡 Suggested addition
{
    name:         "stop when no runs fetched",
    totalFetched: 0,
    batchSize:    250,
    want:         true,
},

if len(filteredRuns) == 0 {
return "", false
}
return filteredRuns[len(filteredRuns)-1].CreatedAt.Format(time.RFC3339), true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnose] selectPaginationCursorDate silently assumes filteredRuns is sorted descending (newest first) so the last element is the oldest. This matches GitHub's default run ordering, but the assumption isn't documented or enforced — a future change that reorders runs before calling this function would silently pick the wrong cursor.

💡 Suggestion

Add a brief comment to document the contract:

// filteredRuns must be ordered newest-first (GitHub API default);
// the last element is treated as the oldest.

The same assumption applies in logs_github_api.go where runs[totalFetched-1].CreatedAt is used for OldestFetchedCreatedAt.

break
}

cursor, ok := selectPaginationCursorDate(nil, oldestFetchedCreatedAt)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnose] No guard against cursor stagnation: if GitHub returns a full batch where all runs share the same CreatedAt timestamp as the previous batch's oldest, beforeDate is set to the same value and the next API call returns an identical page. The loop then spins until MaxIterations, silently truncating results.

💡 Suggested safeguard

Track the previous cursor and break (or log a warning) if it hasn't advanced:

if cursor == beforeDate {
    if verbose {
        fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Pagination cursor did not advance; stopping to prevent infinite loop"))
    }
    break
}

This is a degenerate case, but in high-volume repos with many same-second runs it is reachable.

RepoOverride string // fetch from a specific repository instead of current
// OldestFetchedCreatedAt, when set, is populated with the oldest run creation
// timestamp returned by GitHub in this batch before any workflow/conclusion filtering.
OldestFetchedCreatedAt *time.Time

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnose] OldestFetchedCreatedAt is an output field on an options struct, mixing input and output concerns in ListWorkflowRunsOptions. Future callers reading the struct type won't immediately know this field is populated by the function, not consumed by it.

💡 Alternative

Return it as an additional named return value alongside totalFetched:

func listWorkflowRunsWithPagination(opts ListWorkflowRunsOptions) ([]WorkflowRun, int, time.Time, error)
//                                                                              ^^^^^^^^^^^ oldest fetched

This keeps the options struct as pure input and makes the out-param visible at every call site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants