fix(gmail): reject repeated archive search page tokens - #1086
Conversation
searchMessageIDs assigned pageToken = resp.NextPageToken until empty or --max. A repeated Google token hangs while remaining > 0. Record each next token before assigning, matching Gmail backup ListMessageIDs. Archive --query and autoreply then fail closed. Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs changes before merge. Reviewed September 5, 2026, 4:07 AM ET / 08:07 UTC. ClawSweeper reviewWhat this changesAdds repeated-page-token detection to Gmail query pagination used by bulk message commands and autoreply, with four regression tests. Merge readiness⛔ Needs changes before merge - 2 items remain This fix remains necessary: current main and v0.39.0 still contain the unguarded query loop, while the related merged fixes cover separate pagination paths. The implementation and supplied fault-injection proof support keeping this PR as the landing candidate. Priority: P2 Review scores
Verification
How this fits togetherGmail query pagination collects message IDs before bulk label changes or automatic replies. It consumes Google list responses and returns either matching IDs or an error to those commands. flowchart TD
A[Query and message limit] --> B[Gmail message listing]
B --> C[Collect IDs and inspect next token]
C -->|New token and remaining capacity| B
C -->|Finished or limit reached| D[Return matching IDs]
C -->|Repeated token| E[Return pagination error]
D --> F[Bulk label changes or automatic replies]
Before merge
Findings
Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Preserve bounded query collection and normal pagination while returning a clear cycle error before message mutations or replies. Do we have a high-confidence way to reproduce the issue? Yes: successful empty Gmail list responses with a repeated nonempty nextPageToken leave main's remaining count unchanged and repeat the request. Source inspection and the supplied HTTP fault transcript support this path; the reviewer did not execute it. Is this the best way to solve the issue? Yes: a local seen-token guard preserves the collector's existing result limit and page-size behavior, follows the backup precedent, and propagates failure through existing callers. Full review comments:
Overall correctness: patch is correct AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 25703c789da5. LabelsLabel changes:
Label justifications:
EvidenceAcceptance criteria:
What 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
|
|
Superseded by #1087, which incorporates Gmail query-cycle protection, including repeated empty pages, while retaining existing result limits. Failure occurs before downstream message changes. Thanks @SebTardif. |
What Problem This Solves
gog gmail archive --queryandgog gmail autoreplyresolve matching IDs throughsearchMessageIDs. That helper assignedpageToken = resp.NextPageTokenuntil the token was empty or--maxwas reached. If Google repeatsnextPageTokenwhileremaining > 0, the loop never finishes.An empty list page is the hang.
remainingonly shrinks bylen(resp.Messages). A stuck token with no messages leavesremainingunchanged, sogog gmail archive -q 'in:inbox'keeps callingusers.messages.listuntil the process is killed.Why
Gmail backup ID listing already rejects a repeated token with a local
seenTokensmap (#1004).searchMessageIDscannot switch tocollectAllPagesbecause it still has to honor--maxand the 500-result page cap. This change uses the same seen-token check before assigning the next token.User Impact
A stuck Gmail
nextPageTokennow fails withpagination loop: repeated page tokeninstead of hanginggog gmail archive --queryorgog gmail autoreply. Distinct page tokens still walk normally.Evidence
terminal output from the unpatched search loop versus this patch. A local Gmail
users.messages.listpeer always returnsnextPageToken=stuck.Unpatched (the HTTP peer answers immediately; the hang guard returns 400 on the third list call so the loop cannot run forever):
Patched (same commands, same stuck token, returns immediately after two list calls):
Live CLI still exposes the public query path:
gofmtis clean.golangci-lint run ./internal/cmd/reported 0 issues.go build ./internal/cmd/succeeded.Real behavior proof
nextPageTokenvalues could hanggog gmail archive --queryandgog gmail autoreplywhilesearchMessageIDspagedusers.messages.listandremainingstayed above zero.C:\Users\sebta\.grok\tmp\pr-gate-batch\gogcli-f022onfix/gmail-archive-page-tokenfromorigin/mainat25703c78.go test ./internal/cmd -count=1 -timeout 30s -v -run TestSearchMessageIDsRejectsRepeatedEmptyPageTokenagainst a Gmail list peer that always returnsnextPageToken=stuckand no messages (soremainingnever shrinks), then ran.\bin\gog.exe gmail archive --helpon the binary built from this branch.pagination loop: repeated page token "stuck"after 2 list calls (0.00s). The unpatched loop made a third list call and only stopped when the hang guard returned HTTP 400.remaining > 0.searchMessageIDsandgog gmail archive --query in:inbox --max 50return the repeated-token error on the next page instead of hanging. Two distinct tokens still returnm1,m2.users.messages.listresponse that actually repeats a token. That requires a faulty Google page, which we cannot force from a healthy account.Related
ListMessageIDsin #1004 and bycollectAllPagesininternal/cmd/paging.go.gog gmail search --from-contactfallback uses the same error in #1045.74d8089a(landed in #385, 2026-03-02, 187 days ago).