Skip to content

fix: restore team_id and pagination for workspace grants#78

Merged
btipling merged 4 commits intomainfrom
bt/fix-workspace-grants-deletion
Mar 13, 2026
Merged

fix: restore team_id and pagination for workspace grants#78
btipling merged 4 commits intomainfrom
bt/fix-workspace-grants-deletion

Conversation

@btipling
Copy link
Copy Markdown
Contributor

Summary

  • Fixes regression from commit 63d8340 that caused workspace member and workspace role grants to be deleted during lambda rollout
  • Restores paginated business+ client path for Grants() when available (with team_id filtering and rate limit tracking)
  • Adds proper fallback using standard slack-go client with GetUsersOptionTeamID and caller context when business+ client is unavailable
  • Restores NextPageToken in sync results for SDK pagination support

Root cause: users.list without team_id on Enterprise Grid orgs returns users from the wrong workspace or empty results, causing all workspace/role grants to disappear on re-sync.

Fixes CXH-1162

Test plan

  • Deploy to a test environment and verify workspace member grants are synced correctly
  • Verify workspace role grants (Bot, Invited member, etc.) are present after sync
  • Confirm no grants are deleted on subsequent sync cycles
  • Test with and without business+ token configured

🤖 Generated with Claude Code

Commit 63d8340 removed the business+ client path for workspace grants,
switching to slack-go's GetUsers() without team_id filtering or SDK
pagination. For Enterprise Grid orgs, users.list without team_id can
return empty results, causing all workspace member and role grants to
be deleted on re-sync.

Restore the paginated business+ client path when available, and fix
the fallback to pass GetUsersOptionTeamID and the caller's context.

Fixes: CXH-1162

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@btipling btipling requested a review from a team March 10, 2026 23:59
@linear
Copy link
Copy Markdown

linear Bot commented Mar 10, 2026

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 11, 2026

Claude finished @btipling's task in 1m 51s —— View job


PR Review: fix: restore team_id and pagination for workspace grants

Blocking Issues: 1 | Suggestions: 0

Security Issues

None found.

Correctness Issues

  • Rate limit annotations discarded (pkg/connector/workspace.go:154-162, 277-279): outputAnnotations is created and populated with rate limit data in the business+ path, but never included in the returned SyncOpResults. This means the SDK won't respect rate limits from the users.list API. Compare with group.go which correctly passes Annotations: outputAnnotations in both success and error returns. Fix: hoist outputAnnotations to the outer var block and include it in the final return.

Suggestions

None.

Overall

The core fix is sound — restoring team_id filtering and pagination for workspace grants addresses the root cause of the grant deletion regression. The two code paths (business+ with SDK pagination, fallback with auto-paginating slack-go) are well-structured. The only issue is the dropped rate limit annotations, which should be a straightforward fix.

Comment thread pkg/connector/workspace.go Outdated
Comment on lines +154 to +162
outputAnnotations := annotations.New()
bpUsers, nextCursor, ratelimitData, err := o.businessPlusClient.GetUsers(
ctx,
resource.Id.Resource,
bag.PageToken(),
)
outputAnnotations.WithRateLimiting(ratelimitData)
if err != nil {
return nil, nil, fmt.Errorf("fetching users for workspace: %w", err)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Bug: outputAnnotations is populated with rate limit data but never included in the return value. The annotations are silently discarded, so the SDK won't see rate limit information from the business+ client path.

Compare with group.go:117 and group.go:132 which correctly pass Annotations: outputAnnotations in the SyncOpResults.

Suggested change
outputAnnotations := annotations.New()
bpUsers, nextCursor, ratelimitData, err := o.businessPlusClient.GetUsers(
ctx,
resource.Id.Resource,
bag.PageToken(),
)
outputAnnotations.WithRateLimiting(ratelimitData)
if err != nil {
return nil, nil, fmt.Errorf("fetching users for workspace: %w", err)
outputAnnotations := annotations.New()
bpUsers, nextCursor, ratelimitData, err := o.businessPlusClient.GetUsers(
ctx,
resource.Id.Resource,
bag.PageToken(),
)
outputAnnotations.WithRateLimiting(ratelimitData)
if err != nil {
return nil, &resources.SyncOpResults{Annotations: outputAnnotations}, fmt.Errorf("fetching users for workspace: %w", err)

Comment on lines +277 to +279
return rv, &resources.SyncOpResults{
NextPageToken: pageToken,
}, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Bug: The outputAnnotations from the business+ path (containing rate limit data) should be included here. Currently they are lost.

You'll need to hoist outputAnnotations to the outer scope (alongside users and pageToken) and pass it through:

Suggested change
return rv, &resources.SyncOpResults{
NextPageToken: pageToken,
}, nil
return rv, &resources.SyncOpResults{
NextPageToken: pageToken,
Annotations: outputAnnotations,
}, nil

This requires declaring outputAnnotations in the var block at line 142 and initializing it in both branches.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocking issues found. One correctness issue: rate limit annotations from the business+ client path are created but not returned in SyncOpResults — see inline comments.

Hoist outputAnnotations to outer scope and pass through in the return
value so the SDK receives rate limit data from the business+ client path.
Also return annotations on error paths, consistent with group.go.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 11, 2026

Claude finished @btipling's task in 1m 21s —— View job


PR Review: fix: restore team_id and pagination for workspace grants

  • Gather context and read changed files
  • Review PR changes
  • Post review feedback

Blocking Issues: 0 | Suggestions: 1

Security Issues

None found.

Correctness

The fix is well-structured and addresses the root cause correctly. The second commit (d22007d) properly resolved the rate limit annotation issue that the previous automated review flagged — outputAnnotations is now declared in the outer scope and included in the final SyncOpResults return. This matches the pattern used in group.go.

Both code paths are sound:

  • Business+ path (workspace.go:148-171): Correctly uses team_id filtering via businessPlusClient.GetUsers(), SDK-driven pagination with ParsePageToken/NextToken, and rate limit tracking.
  • Fallback path (workspace.go:172-196): Correctly uses GetUsersContext with GetUsersOptionTeamID for team scoping. Since slack-go auto-paginates, no NextPageToken is needed here.

Suggestions

  • Minor: User field mapping in fallback path (workspace.go:182-195): The fallback path manually maps slack.User fields to client.User. The Name and TeamID fields from slack.User are not carried over. This is fine for the current grant logic (which only checks boolean flags), but if grant logic ever references Name or TeamID, the fallback path would silently produce empty values. Consider adding a comment noting this is intentional, or mapping the full set of fields used by client.User.

Overall

This is a clean, well-scoped fix. The root cause — users.list without team_id on Enterprise Grid returning wrong/empty results — is properly addressed in both the business+ and fallback paths. The second commit correctly addressed the dropped rate limit annotations. The dual-path approach (business+ with SDK pagination vs. fallback with auto-pagination) is well-structured and follows the same patterns used elsewhere in the codebase (e.g., group.go). LGTM.

Switch business+ error paths from raw fmt.Errorf to client.WrapError
for consistent baton-slack: error prefixing across both code paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 11, 2026

Claude finished @btipling's task in 1m 50s —— View job


PR Review: fix: restore team_id and pagination for workspace grants

  • Gather context and read changed files
  • Review PR changes
  • Post review feedback

Blocking Issues: 0 | Suggestions: 1

Security Issues

None found.

Correctness

The fix correctly addresses the root cause: users.list without team_id on Enterprise Grid returns users from the wrong workspace (or empty), causing workspace/role grants to be deleted on re-sync.

Business+ path (workspace.go:148-171): Correctly uses businessPlusClient.GetUsers() with team_id filtering via resource.Id.Resource, SDK-driven pagination through ParsePageToken/NextToken, and rate limit annotation tracking. The error path properly returns outputAnnotations so the SDK can honor rate limits even on failure. This matches the pattern in group.go.

Fallback path (workspace.go:172-196): Correctly uses GetUsersContext with GetUsersOptionTeamID — the same pattern used in user.go:207-208 for the standard API path. Since slack-go auto-paginates internally, no NextPageToken is needed here, which is correct.

Return values (workspace.go:278-281): Both pageToken and outputAnnotations are properly hoisted to the outer scope and included in the final SyncOpResults. The second commit (d22007d) correctly resolved the rate limit annotation issue flagged by the earlier automated review.

Error wrapping: All error paths consistently use client.WrapError (addressed in the third commit dc0b380), which is the correct pattern for this codebase.

Suggestions

  • Minor: Incomplete field mapping in fallback path (workspace.go:182-194): The fallback maps slack.Userclient.User but omits Name, TeamID, RealName, Profile, IsAppUser, and Has2FA. This is fine for the current grant logic (which only checks boolean flags like IsAdmin, IsBot, etc.), but it makes the two code paths produce structurally different client.User objects. If any future grant logic references unmapped fields, the fallback path would silently produce zero values. Consider adding a brief comment noting that only the boolean flags are needed for grant assignment, so readers understand the partial mapping is intentional.

Overall

This is a clean, well-scoped fix across three focused commits. The root cause is correctly addressed in both the business+ and fallback paths, rate limit annotations are properly propagated, and error wrapping is consistent. The dual-path approach follows the same patterns used in user.go and group.go. LGTM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 13, 2026

Claude finished @btipling's task in 1m 20s —— View job


PR Review: fix: restore team_id and pagination for workspace grants

  • Gather context and read changed files
  • Review PR changes
  • Post review feedback

Blocking Issues: 0 | Suggestions: 0

Security Issues

None found.

Correctness

The fix is sound and all previously flagged issues have been addressed across the four commits:

  1. Core fix (f39f946): Restores team_id filtering and SDK pagination for workspace grants. The business+ path correctly uses businessPlusClient.GetUsers() with resource.Id.Resource as the team ID and ParsePageToken/NextToken for pagination. The fallback path correctly uses GetUsersContext with GetUsersOptionTeamID.

  2. Rate limit annotations (d22007d): outputAnnotations is properly hoisted to the outer var block and included in the final SyncOpResults return — matching the pattern in group.go:113-132.

  3. Consistent error wrapping (dc0b380): All error paths use client.WrapError, which is the correct pattern for this codebase.

  4. Docs typo (b288631): Minor fix, "yous" → "your".

Both code paths are correct:

  • Business+ path (workspace.go:148-171): SDK-driven pagination with rate limit tracking. Error path returns annotations so the SDK honors rate limits even on failure.
  • Fallback path (workspace.go:172-196): slack-go auto-paginates internally, so no NextPageToken is needed — pageToken stays as its zero value (""), which is correct.

Field mapping in fallback path (workspace.go:182-194): The mapping omits Name, TeamID, RealName, Profile, IsAppUser, and Has2FA from client.User. This is fine — the grant logic below only uses the boolean flags that are mapped (IsStranger, Deleted, IsPrimaryOwner, IsOwner, IsAdmin, IsRestricted, IsUltraRestricted, IsInvitedUser, IsBot). No data is lost for the current use case.

Overall

Clean, well-scoped fix across four focused commits. The root cause — users.list without team_id on Enterprise Grid returning wrong/empty results — is correctly addressed in both code paths. Rate limit annotations are properly propagated and error wrapping is consistent. LGTM.


@btipling btipling merged commit 07cc7e6 into main Mar 13, 2026
9 checks passed
@btipling btipling deleted the bt/fix-workspace-grants-deletion branch March 13, 2026 05:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants