Skip to content

feat(gmail): add --count to search with whole-query totals - #985

Merged
steipete merged 3 commits into
mainfrom
codex/t12-gmail-count
Aug 13, 2026
Merged

feat(gmail): add --count to search with whole-query totals#985
steipete merged 3 commits into
mainfrom
codex/t12-gmail-count

Conversation

@steipete

Copy link
Copy Markdown
Collaborator

Lands #984 with @chrischall's two commits preserved, plus one maintainer fix.

The feature is right: --count on gmail search/messages reports how many
results a query really has, spending a probe only when the answer is not
already known. Two correctness gaps fixed on top:

  • --all --page TOKEN --count counted only the results after the cursor,
    not the whole query.
  • An empty page reported zero for the whole query instead of the true total.

Gmail's resultSizeEstimate is an estimate by contract, and the output says
so — broad queries report totalMatchesAtLeast, never a fabricated exact.

Live read-only proof against the approved test mailbox:

threads --all:        returned=45  totalMatches=45
messages --all:       returned=286 totalMatches=286
threads --all --page: returned=44  totalMatches=45   (whole-query, post-fix)
broad query:          totalMatchesAtLeast=500

Closes #984. Closes #983. Thanks @chrischall.

chrischall and others added 3 commits August 12, 2026 23:31
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 #983
…ered

Follow-up to the --count review on #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 #983
@clawsweeper

clawsweeper Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

@clawsweeper clawsweeper Bot added P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Aug 13, 2026
@clawsweeper

clawsweeper Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed August 13, 2026, 6:25 AM ET / 10:25 UTC.

ClawSweeper review

What this changes

Adds --count to Gmail thread and message searches, returning an exact whole-query count when one IDs-only page suffices or an explicitly labeled lower bound otherwise.

Merge readiness

Ready for maintainer review

Keep this collaborator-authored PR open for normal maintainer landing review. The focused opt-in feature is correct on source review, preserves parseable stdout, and includes credible live Gmail evidence.

Priority: P3
Reviewed head: f4974915fac2f5b6c80c61209652f6961de152cc

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The patch is focused, edge-case tested, and supported by live terminal evidence of the new count semantics.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (terminal): The PR body provides after-fix, live read-only Gmail terminal evidence for exact, cursor, message-search, and lower-bound outcomes.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Verification

Check Result Evidence
Real behavior Verified Sufficient (terminal): The PR body provides after-fix, live read-only Gmail terminal evidence for exact, cursor, message-search, and lower-bound outcomes.
Evidence reviewed 6 items Whole-query count implementation: The new helpers issue one Gmail list request for IDs and nextPageToken, marking the count exact only when Gmail returns no continuation token.
Paging and output behavior: Both search commands resolve the optional count after page loading and add it only to the JSON envelope; table hints remain on stderr.
Edge-case coverage: The added httptest suite covers exact and lower-bound values, empty cursor pages, full pagination, cursor-plus-all behavior, stderr output, and results-only suppression.
Findings None None.
Security None None.

How this fits together

Gog’s Gmail search commands fetch paged thread or message results and format them for JSON or table output. With --count, they additionally derive a whole-query total without changing the default search path.

flowchart LR
  A[CLI query and flags] --> B[Gmail search command]
  B --> C[Fetch requested page or pages]
  C --> D{Count requested?}
  D -->|No| E[Format results]
  D -->|Yes| F[IDs-only whole-query probe]
  F --> G[Exact total or lower bound]
  G --> E
  E --> H[JSON or parseable table output]
Loading

Before merge

None.

Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Production versus test delta production +191/-10, tests +437, docs/release +3 The new Gmail output contract has substantially more focused regression coverage than implementation code, including pagination edge cases.

Root-cause cluster

Relationship: canonical
Canonical: #985
Summary: This PR is the canonical candidate for the Gmail search-count work and incorporates the earlier implementation with a whole-query correction.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Technical review

Best possible solution:

Land the additive --count contract after normal maintainer review, retaining the exact-versus-lower-bound field distinction and stderr-only text hint.

Do we have a high-confidence way to reproduce the issue?

Not applicable: this PR adds an optional command flag rather than repairing a current-main failure; its claimed live Gmail runs cover the proposed behavior.

Is this the best way to solve the issue?

Yes. An IDs-only first-page probe produces exact small-set totals while honestly labeling broad results as lower bounds, without relying on Gmail’s unreliable result estimate.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 0d8088534f09.

Labels

Label changes:

  • add P3: This is an additive opt-in Gmail CLI enhancement with no demonstrated regression in existing default behavior.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides after-fix, live read-only Gmail terminal evidence for exact, cursor, message-search, and lower-bound outcomes.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body provides after-fix, live read-only Gmail terminal evidence for exact, cursor, message-search, and lower-bound outcomes.

Label justifications:

  • P3: This is an additive opt-in Gmail CLI enhancement with no demonstrated regression in existing default behavior.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (terminal): The PR body provides after-fix, live read-only Gmail terminal evidence for exact, cursor, message-search, and lower-bound outcomes.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides after-fix, live read-only Gmail terminal evidence for exact, cursor, message-search, and lower-bound outcomes.

Evidence

What I checked:

  • Whole-query count implementation: The new helpers issue one Gmail list request for IDs and nextPageToken, marking the count exact only when Gmail returns no continuation token. (internal/cmd/gmail_search_count.go:48, f4974915fac2)
  • Paging and output behavior: Both search commands resolve the optional count after page loading and add it only to the JSON envelope; table hints remain on stderr. (internal/cmd/gmail_search.go:64, f4974915fac2)
  • Edge-case coverage: The added httptest suite covers exact and lower-bound values, empty cursor pages, full pagination, cursor-plus-all behavior, stderr output, and results-only suppression. (internal/cmd/gmail_search_count_test.go:266, f4974915fac2)
  • Maintainer follow-up provenance: The final commit explicitly fixes whole-query counting after the two feature commits, matching the cursor and empty-page cases covered by the patch. (internal/cmd/gmail_search_count.go:105, f4974915fac2)
  • Current-main comparison: Current main does not contain this capability; the PR adds the two flags, shared counting helper, tests, and generated command documentation. (internal/cmd/gmail_search.go:22, 0d8088534f09)
  • Real behavior evidence: The PR body supplies redacted live read-only mailbox terminal results for threads, messages, a cursor page, and a broad-query lower bound.

Likely related people:

  • steipete: Current-main history attributes the shared Gmail request helper to Peter Steinberger, and this PR’s final whole-query correction is authored by steipete. (role: recent Gmail CLI contributor and final count-semantics author; confidence: high; commits: 402def5041d6, f4974915fac2; files: internal/cmd/gmail_search_request.go, internal/cmd/gmail_search.go, internal/cmd/gmail_messages.go)

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@steipete
steipete merged commit 7175a08 into main Aug 13, 2026
16 checks passed
@steipete
steipete deleted the codex/t12-gmail-count branch August 13, 2026 10:28
chrischall added a commit to chrischall/gogcli-mcp that referenced this pull request Aug 14, 2026
…nitized get (#272)

Raises `MIN_GOG_VERSION` **0.35.0 → 0.37.0** (and
`fly-gog-runner/Dockerfile`'s pin with it) and takes up what 0.36.0 /
0.37.0 added.

## New tools

**Gmail — draft-side reply/forward** (gog 0.36.0, openclaw/gogcli#977)

| Tool | |
|---|---|
| `gog_gmail_drafts_reply` | Save a reply as a draft — inherited
recipients, subject and quote; never sends |
| `gog_gmail_drafts_reply_all` | Same, to every participant |
| `gog_gmail_drafts_forward` | Save a forward as a draft; `to` is
optional, so it can be staged with no recipients at all |

They take the same flag set as the send commands and share gog's
composition path, so `replySchema` / `appendReplyFlags` are reused
verbatim rather than re-declared. Staging a reply previously meant
`gog_gmail_drafts_create` + `replyToThreadId`, which threads the draft
but inherits neither the original's recipients nor its quoted body —
both had to be rebuilt by hand, and a missed Cc is invisible until the
draft goes out.

**Sheets — Connected Sheets reads** (gog 0.37.0, openclaw/gogcli#938)

`gog_sheets_datasource_list` / `_describe`, and
`gog_sheets_datasource_table_list` / `_describe` / `_read`. Read-only by
construction (gog exposes no create/update/refresh/delete here). A
data-source table has no id of its own in the Sheets API — its
definition lives on its top-left cell — so extracts are addressed by a
sheet-qualified A1 anchor like `Extracts!B3`.

## Behaviour these depend on

- **`gog_gmail_get` gains `sanitizeContent`.** The flag predates 0.37.0
but emitted the headers and body **twice** in JSON
(openclaw/gogcli#992), so the flag meant to shrink the payload enlarged
it. Verified against a live 0.37.0 build: the sanitized message now
arrives under a single `message` key.
- **`gog_calendar_events` gains `days`**, and it and
`gog_calendar_search` now describe the window rules gog 0.36.0 enforces
(openclaw/gogcli#981). `--days` is a window *length* anchored at
`--from`; it used to discard `--from` silently and answer for today, at
exit 0, in a well-formed table. Fixed presets no longer combine with
`from`/`to`/`days`, and `days` no longer combines with `to`. The old
descriptions invited exactly the combinations that now fail, and the
base calendar test asserted an arg array gog refuses to run.
- **The auth tools gain `extraScopes`** (plus `--force-consent` on the
interactive one, since Google re-prompts for a *new* scope only when
consent is forced). Nothing else can request `bigquery.readonly`, which
Google demands whenever a Sheets response *contains* BigQuery Connected
Sheets data — without it the new sheets tools cannot be authorized
through the wrapper at all. Documented in `docs/auth-scopes.md`.

## Not adopted: `gmail search --count`

gog 0.36.0 (openclaw/gogcli#985) upstreamed this wrapper's match-count
probe, down to the page size and the exact/lower-bound split. The local
probe stays, and the stale comment claiming gog *cannot* supply the
count is corrected to say why:

- it is spent only on a result set already known to be truncated, where
`--count` is decided before the search runs and would cost every search
an extra Gmail request;
- it is best-effort, where gog returns the probe's error from the whole
command — a failed count would turn a search that *did* succeed into an
error.

## Verification

Beyond the unit tests (the mocked suites only assert arg arrays), every
new command was run against the real gog v0.37.0 binary with fake ids,
which parses flags before any API call:

- `gmail drafts reply` / `reply-all` / `forward` — full flag sets parse;
failures are the API's 400 on the fake id (and a send-as validation for
`--from`), not unknown flags.
- `sheets datasource list` / `describe` / `table list` / `table
describe` / `table read` — all reach the API and 404 on the fake
spreadsheet.
- `calendar events --from 2026-09-25 --days 5` returns the anchored
window; `--today --from …` and `--days … --to …` are both rejected with
the messages the tool descriptions now quote.
- `gmail get --sanitize-content --json` on a real message: top-level
keys are exactly `["message"]`.

`npm test` (all workspaces + fly-gog-runner), `npm run typecheck` and
`npm run build` are green; the 100% coverage gate holds.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01EjH6C4jVKavpHiFwBW574N

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P3 Low-risk cleanup, docs, polish, ergonomics, or speculative feature. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(gmail): report how many results a search really has — resultSizeEstimate saturates at 201 and is unusable as a count

2 participants