fix(api): stop users.list pagination on a repeated cursor - #169
Conversation
getUsers walked users.list through slack-go GetUsersContext, which follows next_cursor until empty with no seen-set. A repeated Slack cursor made slacrawl sync request forever. Page users.list in slacrawl with the same seen-map used for conversations.list, history, replies, and DMs. 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 maintainer review before merge. Reviewed September 5, 2026, 3:23 PM ET / 19:23 UTC. ClawSweeper reviewWhat this changesStops Slack member-directory pagination when a cursor repeats, retries individual rate-limited pages, and adds regression coverage and documentation. Merge readiness✅ Ready for maintainer review This PR remains useful: current main and v0.8.6 still lack the member-pagination guard. The focused implementation and supplied production-path HTTP proof support landing it; no blocking defect was found. Priority: P2 Review scores
Verification
How this fits togetherSlacrawl mirrors Slack data into a local SQLite archive. Its member-directory fetch feeds user records and direct-message names into sync, so an endless page walk prevents sync from completing. flowchart TD
A[Slack sync] --> B[Fetch member directory]
B --> C[Slack HTTP page]
C --> D{Page result}
D -->|Rate limited| C
D -->|New cursor| C
D -->|Repeated cursor| E[Return clear error]
D -->|No next cursor| F[Store users and name DMs]
Before mergeNone. Agent review detailsSecurityNone. Review metrics
Technical reviewBest possible solution: Keep member pagination locally bounded while preserving normal member collection, cancellation, and page-level rate-limit retries. Do we have a high-confidence way to reproduce the issue? Yes: a users.list endpoint that repeatedly returns the same nonempty cursor exercises the affected production fetch. Current-main source retains the unguarded call, and the supplied before/after transcript demonstrates the failure and fix; this reviewer did not execute it. Is this the best way to solve the issue? Yes: owning the page loop is a focused extension of the existing Slack pagination pattern, and per-page retries preserve completed progress without introducing configuration or storage changes. AGENTS.md: not found in the target repository. Codex review notes: model internal, reasoning medium; reviewed against b9ff7d91485f. LabelsLabel justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
History |
|
Maintainer verification at
Prepared for squash merge; left open for the owner-authorized landing step. This proves deterministic fault handling with an injected HTTP endpoint, not a live Slack service returning a bad cursor. |
steipete
left a comment
There was a problem hiding this comment.
Maintainer review: reproduced the users.list cursor loop with the built CLI, verified rejection + rate-limit retry; exact-head CI green; autoreview clean.
What Problem This Solves
Fixes an issue where users running
slacrawl sync(or doctor/repair paths that load the member directory) would hang until the process was killed when Slack kept returning the sameusers.listnext_cursor. Channel, history, thread, and DM pagers already stop on a repeated token. The member walk still handed the full walk to slack-go, which followsnext_cursoruntil it is empty.Why This Change Was Made
A stuck Slack cursor is a hang, not a slow page. #159 added a seen-map on
conversations.list,conversations.history,conversations.replies, and DM listing.getUsersstill calledGetUsersContextas one shot, so a repeated member-directory cursor keptslacrawl syncrequesting forever (each request still had the 60s HTTP timeout). This pagesusers.listin slacrawl with the same fail-closed check.The unguarded member walk dates to
588584e(feat: sync post-bootstrap updates, 2026-03-08). It has been present for 180 days. Same class as openclaw/notcrawl#102 and openclaw/discrawl#181.User Impact
A wedged
users.listpager now stops with a clearusers.list repeated cursorerror instead of spinning until someone kills the sync. Successful pages (empty cursor, or a new cursor) are unchanged.Evidence
terminal output from the patched Slack client. An
httptestusers.listpager that always returnsnext_cursor=stucknow fails closed after two pages.Before this patch the same pager never returned. A 5s deadline expired with:
After this patch (
2f79bb80f4f2) the same pager returns immediately:Two distinct pages then an empty cursor still collect both members:
Real behavior proof
slacrawl sync(and doctor/repair that callgetUsers) hung when Slack repeated ausers.listnext_cursor, because slack-goGetUsersContextwalks pages with no seen-set.2f79bb80f4f2on branchfix/users-list-seen-token.GOWORK=off go test ./internal/slackapi/ -count=1 -timeout 15s -run TestGetUsersRejectsRepeatedCursor -vand the matching distinct-cursor command against anhttptestSlackusers.listendpoint.users.list repeated cursor "stuck"after twousers.listPOSTs instead of waiting out the deadline.getUsersreturnsusers.list repeated cursor "stuck"in 0.00s after two pages. Two distinct cursors then an empty cursor still return both members.users.listnext_cursor. Rate-limit retry on a singleusers.listpage was not re-run here; that path uses the sameretryhelper as the other Slack pagers.Related: #159