Teams plugin: PR comment/nop diff shows phantom additions+deletions for unchanged teams due to name vs. slug mismatch
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 76/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- javascript
- Domain
- backend
Research direction
Start in lib/plugins/teams.js, especially find() and the comparator used by the sync path, then read how lib/plugins/diffable.js invokes lib/mergeDeep.js for nop output. Verify the change preserves slug-based identity and that an unchanged team produces no additions or deletions in the PR comment while actionable permission changes remain visible.
Written by the indexing model from the issue text.
Description
Problem Description
What is actually happening
When running in nop mode (dry-run PR comments), the Teams plugin frequently reports every team on a repo as a full addition and a full deletion in the same diff, even when the configured permission genuinely has not changed and no API calls end up being made.
This happens because two separate code paths use different keys to identify a team:
-
The actual sync logic in
lib/plugins/teams.jsmatches an existing team-repo record to a config entry by slug:comparator (existing, attrs) { return existing.slug === attrs.name.toLowerCase() }and only treats
permissionas a real, actionable difference (changed()). This is correct and works fine. -
The nop/PR-comment diff table, generated separately via
MergeDeep.compareDeep()(called fromlib/plugins/diffable.js), pairs array items usingGET_NAME_USERNAME_PROPERTYinlib/mergeDeep.js, which always prefers the object'snameproperty. GitHub's team API objects havenameset to the display name (e.g."Platform Engineering"), which is distinct fromslug(e.g."platform-engineering"). Config entries usenameto mean the slug (per thecomparator()convention above). When the display name and slug don't textually match (multi-word team names, different casing, etc.),compareDeep's pairing fails to recognize the config entry and the existing GitHub record as "the same team" — so it reports the entire existing record as a deletion and the entire config entry as an addition, even though nothing is actually going to change.
This also causes irrelevant/unsettable fields (e.g. notification_setting, which is part of the raw GitHub team object but is never read or sent by teams.js's toParams()/add()/update()) to show up in the diff, making the PR comment noisy and hard to review for the changes that do matter.
What is the expected behavior
For teams whose permission is unchanged, the nop/PR comment diff should show no changes for that team — consistent with what the real sync logic (comparator/changed) determines. The diff table should match on the same identity (slug) that the actual sync uses, not on GitHub's display name.
Example
Given a repo config:
teams:
- name: platform-engineering
permission: push
and an existing GitHub team already attached to the repo with slug platform-engineering, display name "Platform Engineering", and permission push (i.e., truly no change needed), the PR comment nonetheless shows:
- Additions:
{ name: platform-engineering, permission: push } - Deletions: the full existing team object, including fields like
notification_setting: notifications_enabledthat aren't part of the config at all and aren't touched by any API call.
Suggested Fix
Two approaches, in increasing order of scope/risk:
Option A — normalize the identity field in Teams.find() (small, isolated change)
Before existing team-repo records are handed to the shared diff machinery, overwrite their name with slug:
async find () {
return this.github.paginate(this.github.rest.repos.listTeams, this.repo).then(res => {
const normalized = res.map(t => ({ ...t, name: t.slug }))
return this.checkSecurityManager(normalized)
})
}
Since comparator(), changed(), update(), add(), and remove() all key off existing.slug/existing.id and never read existing.name, this is safe — it only affects what compareDeep sees for display purposes. The change is contained entirely to teams.js; mergeDeep.js and diffable.js (shared by every other plugin) are untouched, so there's no risk of regressing branches/collaborators/rulesets/etc.
Option B — pluggable match-key in MergeDeep/Diffable (more general, larger surface)
Add an optional key-extraction callback to MergeDeep's constructor (defaulting to today's GET_NAME_USERNAME_PROPERTY behavior), and let Diffable subclasses override it. Teams would supply item => item.slug. This generalizes the fix for any future plugin with a similar "config-friendly identifier differs from the API's display name" shape, but touches shared code used by every plugin, so it needs broader regression testing across the whole plugin test suite.
Given this is currently a single known instance of the problem, Option A seems like the pragmatic first step; Option B could be worth revisiting if the same class of bug shows up elsewhere (e.g. any other resource where GitHub's API returns a human-readable name distinct from the identifier used in config).
Context
Are you using the hosted instance of probot/settings or running your own?
Running our own instance (fork of safe-settings).
If running your own instance, are you using it with github.com or GitHub Enterprise?
github.com
Version of probot/settings
2.1.20-rc.3 (main-enterprise @ 4578aea)
Version of GitHub Enterprise
N/A
- Dominant language
- JavaScript
- Stars
- 922
- Forks
- 227
- Avg merge
- 18h 3m
- Merged PRs (30d)
- 14
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from github-community-projects/safe-settings
-
Bug Report: environment and repo variables API calls don't paginate, failing with more than 10 items Open
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
github-community-projects/safe-settings#983 · 1 comment · 2 reactions ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
github-community-projects/safe-settings#981 · 6 comments · 3 reactions ·
All issues in github-community-projects/safe-settings
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
HarperFast/skills#96 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Automattic/studio#4908 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
sugarlabs/musicblocks#8847 ·
-
client-controller-update ta-bot-triage team-money-movement
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
MetaMask/metamask-mobile#36594 ·