Skip to content

fix: use team slug instead of DisplayName for membership checks - #41

Open
c1-dev-bot[bot] wants to merge 1 commit into
mainfrom
fix/cxh-1371-use-team-slug-instead-of-display-name
Open

fix: use team slug instead of DisplayName for membership checks#41
c1-dev-bot[bot] wants to merge 1 commit into
mainfrom
fix/cxh-1371-use-team-slug-instead-of-display-name

Conversation

@c1-dev-bot

@c1-dev-bot c1-dev-bot Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bug fix: Team Grant/Revoke operations compared entitlement.Resource.DisplayName against member.Teams from the Sentry API. The member.Teams field contains team slugs (e.g., my-team), not display names (e.g., My Team), so the idempotency check would fail whenever a team's display name differs from its slug.
  • New API method: Added GetTeam client method to retrieve team details by org/team ID, using the Retrieve a Team endpoint.
  • Fixed comparison: Both Grant() and Revoke() now fetch the team's slug via GetTeam and compare it against member.Teams for correct membership detection.

Changes

  • pkg/client/urls.go — Added TeamUrl constant for the retrieve-a-team endpoint
  • pkg/client/teams.go — Added GetTeam() method
  • pkg/connector/teams.go — Updated Grant() and Revoke() to use team.Slug instead of entitlement.Resource.DisplayName

Fixes: CXH-1371

Test plan

  • Verify team Grant correctly detects existing membership (team slug matches)
  • Verify team Revoke correctly detects existing membership before revoking
  • Verify Grant with a team whose display name differs from its slug (e.g., "My Team" slug: "my-team")
  • Verify Revoke returns GrantAlreadyRevoked when member is not in team

Automated PR Notice

This PR was automatically created by c1-dev-bot as a potential implementation.

This code requires:

  • Human review of the implementation approach
  • Manual testing to verify correctness
  • Approval from the appropriate team before merging

The team Grant/Revoke code incorrectly compared
entitlement.Resource.DisplayName against member.Teams from the Sentry
API. The member.Teams field contains team slugs, not display names, so
the comparison would fail whenever a team's display name differs from
its slug (e.g., "My Team" vs "my-team").

Added a GetTeam client method that retrieves a team by ID and uses the
returned Slug field for the membership comparison.

Fixes: CXH-1371
@c1-dev-bot
c1-dev-bot Bot requested a review from a team April 16, 2026 04:02
@linear

linear Bot commented Apr 16, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Claude finished @c1-dev-bot[bot]'s task in 1m 41s —— View job


PR Review: fix: use team slug instead of DisplayName for membership checks

  • Gather context and read changed files
  • Review code changes for correctness, bugs, and best practices
  • Post review feedback

Summary

This is a clean, well-targeted bug fix. The core issue — comparing DisplayName against Sentry's member.Teams field which contains slugs, not display names — is real and the fix is correct.

Verdict: Approve with minor suggestions

The approach is sound. The code is consistent with existing patterns in the codebase and correctly addresses the slug/name mismatch.


Detailed Review

Bug fix correctness

The DetailedMember.Teams field (pkg/client/models.go:215) is []string with JSON tag "teams", which per the Sentry API contains team slugs. The old code compared this against entitlement.Resource.DisplayName, which is set from team.Name (the display name) in newTeamResource(). These can differ (e.g., display name "My Team" vs slug "my-team"), causing incorrect idempotency checks. The fix correctly fetches the team's slug via GetTeam and uses it for comparison.

New GetTeam method (pkg/client/teams.go:13-26) ✓

Follows the same pattern as other client methods (ListTeams, GetOrganizationMember). Error handling and rate limit propagation are consistent. The TeamUrl constant (pkg/client/urls.go:13) correctly uses the teams/{org}/{team_id_or_slug}/ endpoint, which accepts the numeric team ID stored in the resource ID.

Trade-off: extra API call per Grant/Revoke

Both Grant() and Revoke() now make an additional GetTeam HTTP request. This is acceptable for correctness, and the rate limiting is properly propagated. An alternative would be to store the slug in the group profile during sync and retrieve it during provisioning, avoiding the extra call — but that would be a larger change and this fix is pragmatic.

Minor Issues

  1. Rate-limit annotations discarded on early return (pre-existing, see inline comment):
    In Grant() at line 142, return annotations.New(&v2.GrantAlreadyExists{}), nil creates a fresh annotation set, discarding rate-limit info accumulated in ann from GetTeam and GetOrganizationMember. Same pattern exists in Revoke() at line 191 with GrantAlreadyRevoked. This was pre-existing but is now more relevant since there's an additional API call. Low priority — not a blocker.

  2. No bounds check on split (pre-existing): strings.Split(entitlement.Resource.Id.Resource, "/") assumes the ID always contains /. No action needed since this is established throughout the codebase and the resource IDs are always constructed in orgId/teamId format by newTeamResource.

What looks good

  • Error messages correctly use the baton-sentry: prefix in the connector layer
  • Rate limiting is properly handled for the new API call
  • The nolint:bodyclose annotation is appropriate (consistent with other client methods)
  • Variable rename from name to slug in the loop improves readability

Comment thread pkg/connector/teams.go
if name == teamName {
for _, slug := range member.Teams {
if slug == team.Slug {
return annotations.New(&v2.GrantAlreadyExists{}), 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.

Nit (pre-existing, not introduced by this PR): This annotations.New(...) discards the rate-limit annotations accumulated in ann from the GetTeam and GetOrganizationMember calls above. Consider returning ann with the annotation appended instead:

Suggested change
return annotations.New(&v2.GrantAlreadyExists{}), nil
ann.Append(&v2.GrantAlreadyExists{})
return ann, nil

(Check if ann.Append exists in the SDK — if not, this is fine to leave as-is since it's pre-existing behavior.)

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.

0 participants