Treat GitHub Actions 2,500+ as a saturation signal, not a run count. If a report or evidence collector needs every matching workflow run, partition the query by creation time until each window fits under the 1,000-result search ceiling, enumerate every page, and reconcile unique run IDs before claiming coverage.
GitHub changed filtered workflow-run queries on September 25, 2026. Searches by workflow, event, status, branch or actor now report 2,500+ when the number of found records exceeds 2,500. GitHub says previous attempts to calculate an exact larger count frequently timed out and returned the records found before the timeout rather than the true count. The new value is less precise but more honest. (GitHub Changelog)
The retrieval ceiling did not move. Paginated filtered searches still return at most 1,000 workflow runs. A result can therefore show an exact count above 1,000 while the integration remains unable to retrieve the full set from that query. Count precision and evidence coverage are separate facts.
Store a relation, not only an integer
2,500+ is a lower bound. Converting it to the integer 2500 destroys the part of the response that matters.
Use a small internal count type:
export function normalizeReportedCount(input) {
if (Number.isInteger(input) && input >= 0) {
return { relation: "eq", value: input };
}
if (typeof input === "string" && /^\d[\d,]*\+$/.test(input)) {
return {
relation: "gte",
value: Number(input.replace(/[,+]/g, "")),
};
}
throw new TypeError("reported count must be a non-negative integer or N+");
}This adapter is deliberately independent of one response field. GitHub documents the display meaning and the API behavior, but an integration should bind its actual provider response shape in its own client tests. The durable contract is the relation: eq means an exact reported value; gte means the true value is at least that large.

GitHub's repository workflow-runs endpoint accepts per_page up to 100. It returns at most 1,000 results for a search that uses actor, branch, check-suite ID, creation time, event, head SHA or status. A complete 1,000-result window therefore needs as many as ten successful pages, not one large request. (Workflow runs REST API)
Split on creation time before pagination
GitHub recommends narrowing a query, for example with a date range, when an integration needs more than 2,500 matching runs. The created parameter accepts a date-time range, and GitHub's search syntax supports ISO 8601 timestamps down to the second. (Search syntax)
Turn that advice into a release contract:
- Run the original filtered query for a bounded creation window.
- If its reported count is a lower bound or its exact count exceeds 1,000, split the time window.
- Keep the children non-overlapping. With inclusive second-precision ranges, end the left child one second before the right child begins.
- Repeat until every leaf has an exact reported count at or below 1,000.
- Request each leaf at 100 results per page.
- Retain every workflow-run ID and reconcile the unique set against the exact leaf counts.
The reference planner uses inclusive windows because the documented range syntax includes both endpoints:
export function planWindow(window, reportedCount, ceiling = 1_000) {
const count = normalizeReportedCount(reportedCount);
const saturated = count.relation !== "eq" || count.value > ceiling;
if (!saturated) {
return { status: "enumerate", window, count, perPage: 100 };
}
const split = splitWindow(window);
return split.status === "split"
? { ...split, count }
: { status: "incomplete", reason: split.reason, count, window };
}This is an implementation recommendation, not a GitHub guarantee. A repository can produce more than 1,000 matching runs within one timestamp second. Because the documented search syntax only promises second precision, that leaf cannot be made exhaustive by time splitting alone. Narrow it with another documented filter if that still represents the question. Otherwise preserve incomplete instead of inventing coverage.
Make each leaf prove itself
A page loop finishing without an exception does not prove completeness. Pages can fail, IDs can repeat, and an integration can stop early.
For every leaf, retain:
| Evidence | Why it matters |
|---|---|
| Repository and fixed filters | Reconstructs the exact question |
Inclusive created window | Defines the partition boundary |
| Reported value and relation | Distinguishes exact from lower-bound counts |
| Requested page numbers | Shows what the collector attempted |
| Provider request IDs and errors | Separates acceptance from failure |
| Unique workflow-run IDs | Grounds coverage in actual records |
| First and last returned timestamps | Exposes boundary mistakes |
| Terminal state | Keeps complete, failed and saturated windows separate |
The leaf verifier refuses three common false positives:
if (errors.length > 0) {
return { status: "incomplete", reason: "request_failed" };
}
if (count.relation !== "eq" || count.value > 1_000) {
return { status: "incomplete", reason: "saturated_window" };
}
if (new Set(runIds).size !== count.value) {
return { status: "incomplete", reason: "count_mismatch" };
}
return { status: "complete" };The aggregate verifier then checks that partitions are adjacent, do not overlap, contain no duplicate run ID across windows, and have a unique-ID total equal to the sum of their exact counts.

This is the same evidence discipline needed for Copilot usage metrics. An aggregate is useful for direction, but a production decision still needs the population, missingness and join rules that created it. It also complements the admission boundary in our comment-triggered coding-agent controller: authenticated submission proves who asked for work; workflow-run enumeration proves what the execution system actually recorded.
Do not use total count as a cursor
The reported count is not a stable paging token. New runs can enter a live time window while an integration is traversing it, and retention or deletion can remove older records. The safe approach is to bind a collection job to closed time windows and retain its evidence.
For a recurring collector:
- keep the current open window provisional;
- close it at a defined UTC boundary;
- enumerate and reconcile that closed window;
- never silently replace a completed evidence bundle with a later count; and
- issue a new correction bundle if a provider-side change requires re-observation.
If the business question is “how many successful releases did the coding agent produce last quarter?”, the evidence record should also retain the workflow identity, branch, event, status and repository. A complete set of all workflow runs is not the same as a complete set for the release policy.
Release the coverage gate
The dependency-free reference artifact for this article passed 16 of 16 local tests. It covers exact and lower-bound count parsing, invalid values, the 1,000-result boundary, exact and lower-bound splitting, adjacent second-precision windows, an unsplittable saturated second, successful leaf reconciliation, saturated leaves, duplicate IDs, failed pages, complete partitions, gaps, duplicates across partitions and unfinished work.
It did not call GitHub. It is not evidence that a particular repository has complete history. Before using the method for an audit, cost report or coding-agent release decision, prove these in the target environment:
- the client preserves the provider's exact-versus-lower-bound relation;
- every filtered leaf with more than 1,000 matches is split before pagination;
- every requested page has a retained result or explicit failure;
- window boundaries neither overlap nor leave a second uncovered;
- run IDs are unique across the combined set;
- saturated single-second leaves remain visibly incomplete;
- the completed bundle records the API version, repository, filters and observation time; and
- a failed or incomplete bundle cannot be promoted into a release metric.
GitHub's change makes an old ambiguity visible. That is useful. The operator's job is to carry the honesty through the rest of the pipeline rather than shaving the plus sign off at ingestion.
FAQ
Why does GitHub Actions show 2,500+ workflow runs?
GitHub now reports 2,500+ when a filtered workflow-run query finds more than 2,500 records. It says attempts to compute a larger exact count frequently timed out and could return only the records found before the timeout.
How many filtered workflow runs can the GitHub REST API return?
GitHub documents a ceiling of up to 1,000 results per filtered search. A page can contain at most 100 results.
How do I retrieve more than 1,000 GitHub Actions workflow runs?
Narrow the query. For exhaustive time-bounded evidence, split the created range until every window reports an exact count at or below 1,000, then paginate each window and reconcile unique run IDs.
Does total_count prove that every workflow run was retrieved?
No. A reported count describes matches, while retrieval is subject to the 1,000-result search ceiling. Coverage requires an exact count for every leaf window and a matching set of unique returned run IDs.








