The Watcher Polled the First Page Forever

A watcher can run every few minutes.

Every request can succeed.

The JSON can parse.

The count can stay the same.

The report can still say there is nothing new.

There may be one small problem.

The watcher may have been asking the API for the first page forever.

A successful request is not an exhaustive result

The current gh api help is explicit about pagination.

--paginate makes additional requests until there are no more pages.

Without that flag, one successful invocation is evidence for one response page.

It is not evidence that the collection ended there.

That distinction disappears easily in a shell script.

This command emits valid JSON.

gh api 'repos/cli/cli/releases?per_page=100'

This one asks for every page.

gh api --paginate 'repos/cli/cli/releases?per_page=100'

Both can exit zero.

Only the second command even attempts the exhaustive question.

Polling makes the blind spot durable

A one-off report can miss later pages once.

A polling loop can preserve the mistake indefinitely.

The loop looks healthy because transport, authentication, and JSON parsing all work.

Its reader is simply narrower than its claim.

If the endpoint's ordering keeps new items outside the page being fetched, every poll can repeat the same old answer.

The automation is not stale because it stopped.

It is stale because it keeps succeeding against the wrong slice.

I made the wrong check fail first

I used the public cli/cli releases endpoint as a disposable fixture.

At the observation time, one request with per_page=100 returned 100 releases.

Fetching every page returned three page arrays with these lengths.

[100,100,1]

The flattened collection contained 201 releases.

Those numbers describe one observation on August 26, 2026.

The collection can grow, so the article should not turn them into permanent GitHub facts.

The useful property is the inequality.

first page count < paginated count

I first asserted the opposite.

first_count=$(gh api 'repos/cli/cli/releases?per_page=100' --jq 'length')
full_count=$(
  gh api --paginate --slurp 'repos/cli/cli/releases?per_page=100' |
    jq '[.[][]] | length'
)

[[ "$first_count" -eq "$full_count" ]]

That check failed with the expected inequality.

Then I changed the assertion to the property the fixture could actually establish.

[[ "$full_count" -gt "$first_count" ]]

It passed.

The failure mattered more than the pass.

It proved the fixture contained a later page for the detector to miss.

Aggregate pages deliberately

In the current GitHub CLI, --slurp wraps all page responses in an outer JSON array.

The release response is already an array, so the resulting shape is an array of arrays.

[
  [page one items],
  [page two items],
  [page three items]
]

That shape makes page coverage visible before flattening.

paged_json=$(gh api --paginate --slurp 'repos/cli/cli/releases?per_page=100')

jq 'map(length)' <<< "$paged_json"
jq '[.[][]] | length' <<< "$paged_json"

There is a current CLI detail worth preserving in the example.

gh api --slurp cannot be combined directly with its --jq flag in GitHub CLI 2.98.0.

The verified command pipes the slurped JSON to the external jq process instead.

Changing that shape casually would turn a tested example into an attractive typo.

Do not generalize an endpoint's ordering

Pagination and ordering are separate contracts.

The public fixture proves that this releases collection needed three pages at the observation time.

It does not prove that every GitHub endpoint orders items the same way.

It does not prove that page one always contains the oldest or newest items.

If a watcher depends on ordering, verify that endpoint's documented order or sort on a field whose semantics you have established.

--paginate repairs missing pages.

It does not invent an ordering guarantee.

“None found” is a stronger claim than it looks

A filtered empty array may mean several different things.

The collection was exhausted and no item matched.
Only one page was read and no item there matched.
The command returned a different shape than the filter expected.
The command never ran and the wrapper treated empty output as data.

Only the first state supports “none found.”

The other states are gaps in evidence.

A reliable watcher should record enough context to distinguish them.

endpoint and query parameters
pagination mode
page count
item count before filtering
item count after filtering
observation time
reader command or version

The extra fields are not operational decoration.

They are the boundary of the claim.

The practical rule

If the sentence contains “all,” “total,” or “none,” one API page is not enough evidence.

Use the endpoint's pagination mechanism.

Inspect the page shape before flattening it.

Make the detector fail against a fixture that has a later page.

Record what the reader actually covered.

Then let the watcher say nothing new.

Until then, it has only said nothing new on the page it happened to read.