Claude Code was asked to verify that every customer record had moved to a new schema. An MCP tool returned 100 records. All 100 had the new field, so the agent reported that the migration was complete.
The tool had also returned next_cursor. Claude Code never followed it.
There were 8,742 records in the collection. The first page happened to contain recent customers, which had all passed through the new write path. Thousands of older records still needed migration.
Nothing in the first page was false. The failure was in the denominator.
Make partial results impossible to mistake for complete results
A tool response should say whether it represents a whole collection or a page. Do not bury that fact in optional metadata.
{
"items": ["...100 records..."],
"page": {
"returned_count": 100,
"next_cursor": "cur_01K4...",
"is_complete": false
},
"collection": {
"snapshot_id": "customers:2026-08-29T09:18:00Z",
"expected_count": 8742
}
}
The control plane should refuse any claim about the whole collection while is_complete is false. That rule belongs in the tool adapter or evaluator, not in a prompt asking the model to remember pagination.
This distinction matters beyond migrations. A security review can miss old access grants. A dependency audit can inspect the first page of repositories. A cleanup run can revoke 100 tokens while leaving the next page active. The visible records may all be correct while the conclusion remains wrong.
Pin one collection snapshot
Following every cursor is necessary, but it is not enough. If records are added or deleted while the agent scans, later pages may belong to a different collection state. Offset pagination can skip or duplicate records as rows move.
Ask the source for a snapshot token or a stable upper bound:
scan_contract:
source: customer-registry
query: schema_version != 3
snapshot_id: customers:2026-08-29T09:18:00Z
expected_count: 8742
page_size: 100
max_pages: 89
max_records: 9000
deadline_seconds: 120
Every page request must carry the same snapshot_id. If the source cannot provide snapshot semantics, use a stable ordering key and record a high-water mark before the scan. State the weaker guarantee in the review packet. Do not label a moving scan complete when records could have crossed its boundary.
The limits matter too. Blindly following cursors can create a cost loop or trap the agent in a broken sequence. Set maximum pages, records, elapsed time, and repeated-cursor tolerance before the first call. Crossing a limit should stop with coverage_unknown, not quietly return whatever has been collected.
Reconcile more than the item count
A returned count of 8,742 can still hide duplication. The scan may have read one page twice and skipped another. Reconcile identifiers as well as totals.
coverage_checks:
pages_expected_max: 89
pages_observed: 88
records_expected: 8742
records_observed: 8742
unique_ids_observed: 8642
duplicate_ids: 100
missing_id_ranges:
- customer-4100..customer-4199
terminal_cursor_observed: true
result: failed
Here the total count looks perfect. The unique count exposes the missing page.
For large collections, you may not want every identifier in the review packet. Keep the detailed set in durable evidence storage and attach a digest, cardinality, duplicate count, and partition summary. If the source can provide per-partition counts or a Merkle root, compare them. A single grand total is a weak proof of coverage.
Also record filters. status=active and region=eu change the denominator. If the agent describes findings for all customers after scanning only active European customers, pagination is not the only problem. The receipt must bind the conclusion to the actual query.
Produce a collection completeness receipt
The run needs one artifact that lets a reviewer check what the agent actually covered.
collection_completeness_receipt:
run_id: cc-run-5107
tool: customer-registry.list
query_digest: sha256:4c19...
snapshot_id: customers:2026-08-29T09:18:00Z
pages_observed: 88
records_expected: 8742
records_observed: 8742
unique_ids_observed: 8742
duplicate_ids: 0
cursor_chain_digest: sha256:97ab...
terminal_cursor_observed: true
limits_hit: []
coverage_status: verified
conclusion_scope: all_records_in_snapshot
Bind the receipt to the query, tool contract, snapshot, run, and evidence store. Fetched all pages is not useful if nobody can tell which filter or collection state those pages represented.
The cursor_chain_digest preserves the traversal without filling the packet with every token. Keep the ordered cursor events in the trace. A reviewer or evaluator can recompute the digest and detect a missing, repeated, or reordered page.
If any count disagrees, a cursor repeats, the snapshot changes, or a limit fires, set coverage_status to unknown or failed. The agent may still report page-level findings, but it cannot make a collection-wide claim.
Test the failure paths
A happy-path pagination test proves little. Add fixtures for the ways collection scans break:
fixtures:
- name: first_page_is_clean_but_next_page_has_failure
expected: migration_incomplete
- name: cursor_repeats_page_three
expected: coverage_failed
- name: total_count_matches_but_ids_are_duplicated
expected: reconciliation_failed
- name: snapshot_changes_between_pages
expected: scan_restarted_or_blocked
- name: page_limit_is_reached
expected: coverage_unknown
- name: final_page_has_no_cursor
expected: terminal_cursor_recorded
- name: filter_changes_during_retry
expected: receipt_rejected
Run these through the real MCP adapter and policy path. A mock that always returns one complete page trains the evaluator to miss the production failure.
Put the receipt beside the test evidence that names what did not run and the trace completeness proof used for replay. Together they answer three different questions: what data was inspected, what tests ran, and whether the run can be reconstructed.
My rule is simple: Claude Code cannot make a claim about a collection until it proves the denominator. Follow the cursor against one snapshot, reconcile unique identifiers, enforce scan limits, and attach a completeness receipt to the review packet.
Claude Code: Building Production Agents That Actually Scale covers MCP boundaries, evals, observability, rollback, cost control, permissions, and review packets for production coding agents.