feat(gmail): add --count to search and messages search - #984
Conversation
Report how many results a query really has. `gmail search` and `gmail messages search` currently emit items plus nextPageToken, so a caller can tell that more results exist but not how many — and a capped page then gets read as the whole answer. Deliberately NOT Gmail's resultSizeEstimate, which is the obvious source and saturates. Measured on a live account (v0.35.0, 2026-08-12) it returned exactly 201 for every non-empty query — from:freshbooks.com (21 real matches), from:housecallpro.com (6), from:thumbtack.com newer_than:30d (3), from:honeybook.com (9) — and 0 for a query with no matches. It does not vary with maxResults either (identical at 1, 10 and 100). Surfacing it would let a caller report "3 of ~201" when the truth is 3 of 6, which is worse than reporting nothing. Instead --count asks for one maximal page of bare ids (maxResults=500, fields=<items>/id,nextPageToken) and counts them. Exact when the set fits a page, reported as totalMatches; a lower bound when the page fills with more behind it, reported as totalMatchesAtLeast, so a saturated probe can never be mistaken for a total. Exactness holds for the narrow queries where a wrong count does the most damage. Opt-in, so no caller pays the extra round-trip without asking. The text path prints to stderr, keeping stdout parseable. Refs openclaw#983
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: found issues before merge. Reviewed August 12, 2026, 11:51 PM ET / August 13, 2026, 03:51 UTC. ClawSweeper reviewWhat this changesThe PR adds an opt-in Merge readinessKeep open: the focused implementation and live Gmail proof are credible, but a maintainer must choose whether the new count belongs on text output as a stderr hint, and the generated command references still omit the new flag. Priority: P3 Review scores
Verification
How this fits togetherGmail search commands turn a query and paging options into Gmail list requests, then render JSON or a table for CLI and agent callers. The new path optionally derives a whole-query count before rendering the result envelope or stderr hint. flowchart LR
Query[Search query and flags] --> List[Fetch Gmail result page]
List --> Requested{Count requested?}
Requested -->|No| Render[Render JSON or table]
Requested -->|Yes| Resolve[Probe IDs or reuse all-page results]
Resolve --> Envelope[Add exact total or lower bound]
Envelope --> Render
Decision needed
Why: The implementation is opt-in and preserves stdout parsing, but the linked feature discussion explicitly leaves this user-facing output contract to maintainers. Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Root-cause clusterRelationship: Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Technical reviewBest possible solution: Choose the cross-format contract, then retain the ID-only probe design and publish the generated references for both Gmail search commands. Do we have a high-confidence way to reproduce the issue? Not applicable as a bug reproduction: this is a new optional count capability, and the PR supplies live Gmail after-fix output for its intended behavior. Is this the best way to solve the issue? Unclear until maintainers choose the text-output contract; the ID-only bounded probe is otherwise a narrow, honest approach that avoids Gmail's unreliable estimate. Full review comments:
Overall correctness: patch is correct AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 0d8088534f09. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rank-up movesOptional improvements that raise the rating; they are not merge blockers.
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (1 earlier review cycle)
|
…ered Follow-up to the --count review on openclaw#983, which asked for an explicit contract around --page, --all and --results-only. Two of the three were real defects: --results-only unwraps the envelope to the bare result array, so a count field cannot survive it. The probe ran anyway and its result was silently discarded — a Gmail request spent for nothing, with no way for the caller to tell. It is now skipped, and the caller is told on stderr rather than left guessing. --all has already walked every page, so the items in hand ARE the whole set. The probe was asking Google a question the walk had just finished answering. The total now comes from the walk, which also makes it exact by construction. --page needed no code change but did need saying: the count is always for the WHOLE query, never the remainder after a cursor. That is the number that stops a caller concluding an absence, and keeping it page-independent means it does not drift while paging. Now stated in the flag help. Refs openclaw#983
|
Pushed Both turned out to be real defects rather than just undocumented behaviour, verified against a live account before fixing:
On the product question in the review — JSON-only vs. also text: happy to defer entirely. Worth one data point for the decision: the text path currently prints Two new tests cover the skip paths, including one that would fail if a probe result could masquerade as an Noting the |
|
Live proof for
Before this commit the same command made the extra Gmail request and then discarded its result silently.
Same answer as before the change, now without the redundant round-trip. On what each layer proves. The live runs above confirm the observable contract. They cannot themselves prove the request did not fire — gog has no HTTP-debug flag to count calls — so that is asserted deterministically in the unit tests instead, where the httptest handler increments a counter on any ids-only probe and both new tests require it to stay at zero. Earlier live proof for the core feature is unchanged and still in the PR description: exact counts of 21, 6 and 9 all matching |
Closes #983.
gmail searchandgmail messages searchemit items plusnextPageToken, so a caller can tell that more results exist but not how many. That gap is where agent/LLM callers go wrong: a capped page reads as the complete answer, and the caller reports that a message doesn't exist when it does.Why not
resultSizeEstimateIt's the obvious source, and it saturates. Measured on a live account,
v0.35.0 (402def5), 2026-08-12:resultSizeEstimatefrom:freshbooks.comfrom:housecallpro.comfrom:thumbtack.com newer_than:30dfrom:honeybook.comzzzznomatch201 for every non-empty query, and identical at
maxResults1, 10 and 100 — so it isn't a per-page figure either. It's a has-results boolean wearing a number's clothes. Emitting it would let a caller report "3 of ~201" when the truth is 3 of 6, which is worse than reporting nothing.What
--countdoesOne extra list call for a single maximal page of bare ids:
totalMatchestotalMatchesAtLeastTwo separate field names so a saturated probe can never be mistaken for a total. Exact for the narrow queries where a wrong count does the most damage.
Opt-in, so nobody pays the round-trip without asking. Text path prints to stderr, keeping stdout parseable.
Live verification (
bin/gogfrom this branch)Opt-in respected — without
--count, no count fields and no probe:Text path — hint on stderr, table clean on stdout:
Tests
8 new tests in
internal/cmd/gmail_search_count_test.govia the existinghttptestidiom — exact count, lower bound, probe shape (asserts ids-onlyfields,maxResults=500, and that it reuses the search's own query), opt-in (probe must not run without the flag), zero-match, messages-search variant, and the stderr wording.make lintclean (0 issues);go test ./internal/cmd/passes (81s).Notes
CHANGELOG.mddeliberately untouched — maintainer-owned per AGENTS.md landing flow.