Teams are how access is granted in a well-run organisation. Team synchronisation is how team membership stops being a thing anybody maintains by hand.
Which sounds like a small operational improvement and is actually the difference between an access model you can review and one you can only inspect.
The short answer
Section titled “The short answer”Identity provider groups map to GitHub teams, and GitHub documents that this mapping is used to manage both team membership and organisation membership.
Membership flows one way. The directory decides; GitHub follows. Manual changes in GitHub are drift.
Deprovisioning removes team membership, and GitHub documents that this happens even if the user is still in the corresponding group on the IdP side.
The design work is the group structure, not the mapping mechanism. A mapping to poorly-designed groups produces poorly-designed teams.
Why this matters more than it looks
Section titled “Why this matters more than it looks”Grants are to teams. Repository permissions given to individuals are unmaintainable at scale and invisible at review time. A well-run organisation grants access to teams.
So team membership is access. Whoever controls team membership controls who can read and write every repository that team has been granted.
Manual team membership means access decisions made in GitHub, by whoever has permission, with no record in the directory and no connection to the joiner-mover-leaver process.
Synchronised team membership means access decisions made in the directory, where they are already reviewed, already audited, and already connected to employment.
That is the whole argument, and it is why this is an identity topic rather than a convenience feature.
Designing the group structure
Section titled “Designing the group structure”The part that determines success, and the part that is usually rushed.
One group per team, matching the organisation. If there is a payments team, there is a payments group containing the payments team. The mapping is then obvious to everybody.
Express durable facts, not permissions. “Member of the platform team” is durable. “Can deploy to production” is a permission, and putting it in the directory means the directory now holds authorisation decisions.
Include a baseline group covering the whole engineering population, which provisions accounts and grants organisation membership. Team groups add specificity above it.
Avoid a group per repository. It does not scale, it duplicates what repository permissions already express, and it produces hundreds of directory groups nobody can navigate.
Avoid encoding seniority. “Senior engineers” as an access group couples promotions to access changes.
Name groups so the mapping is obvious. A convention such as a shared prefix makes it clear which groups are load-bearing for GitHub and which are not — and it stops somebody deleting one during a directory tidy-up.
Teams, CODEOWNERS and grants
Section titled “Teams, CODEOWNERS and grants”Team synchronisation only pays off if teams are actually how access is granted. Three places teams appear, and they should agree.
Repository permissions. A team granted Write on a set of repositories. This is where synchronisation delivers its value: change the directory group, and repository access changes.
CODEOWNERS. A team named as owner of a path, producing automatic review requests. Naming a team rather than individuals means the review request follows the team as its membership changes. See CODEOWNERS.
Ruleset bypass and required reviewers. Teams named in governance configuration.
All three should reference the same teams. An estate where repository grants go to teams, CODEOWNERS names individuals, and ruleset bypass names a fourth thing is one where changing somebody’s role fixes one third of their access.
Audit for individual references. Individuals named in CODEOWNERS are the most common, because it is quicker to write a username than to create a team. They are also the entries that break when somebody leaves — the required review can never be satisfied.
A CODEOWNERS entry naming a departed person blocks merges, and it is a surprisingly common cause of a repository nobody can contribute to. A periodic check that every CODEOWNERS entry resolves to a current team or user is cheap and worth automating.
Nested groups and inheritance
Section titled “Nested groups and inheritance”Behaviour depends on your identity provider, not on GitHub, and it differs between providers.
Test it rather than assuming. Create a nested structure with a test user in the child group and confirm whether they appear in the GitHub team mapped to the parent.
GitHub teams also nest, with child teams inheriting the parent’s repository access. That is a GitHub-side hierarchy and it composes with the directory hierarchy in ways worth verifying deliberately.
Prefer flat where you can. Two hierarchies interacting is a source of surprises, and most organisations do not need the nesting on both sides.
Enterprise teams
Section titled “Enterprise teams”Teams defined at the enterprise level rather than within an organisation, and useful for a specific problem.
A GitHub team belongs to an organisation. Which means a group of people working across three organisations needs three team definitions, maintained separately, and three mappings to the same directory group.
Enterprise teams reduce that duplication by existing at the enterprise level and being usable across its organisations.
Which matters most for cross-cutting populations: a security team that needs access everywhere, a platform team maintaining shared infrastructure, an on-call group with elevated access during incidents.
And for ruleset bypass, where a team named in an enterprise ruleset should sensibly be an enterprise-level concept rather than a team in one particular organisation.
Check what your configuration supports before designing around them — availability and capabilities differ by platform and change over time.
The design guidance is the same. Map them from directory groups, keep the group structure simple, reconcile, and do not let membership be maintained by hand.
What breaks
Section titled “What breaks”Group renames. The mapping references a group; renaming it on the IdP side can break the link, and the symptom is a team that quietly stops receiving members.
Group deletions. More dramatic and more obvious — the team empties.
Large groups. Some providers have limits on group size for provisioning, and a group that grows past one may partially synchronise.
Manual additions in GitHub. The most common problem and the least visible. Somebody adds a member directly to unblock them, and that membership now exists outside the directory.
Users in the group but not provisioned. Somebody in the directory group who has never been assigned the GitHub application. They are in the group and not in the team, and the cause is two steps away from the symptom.
Deprovisioning versus group membership. GitHub removes a deprovisioned user from mapped teams even if the IdP group still lists them. If you see this, the fix is at the IdP.
Synchronisation stopping entirely. No error, no alert, and state slowly diverging. This is the failure that reconciliation exists to catch.
Diagnosing a membership problem
Section titled “Diagnosing a membership problem”The questions, in order, when somebody says they cannot access a repository they should be able to.
Is the repository granted to a team they should be in? Frequently the answer is that the repository was granted to a different team, or to individuals, and the model was never applied to it.
Are they in that GitHub team? Look. This is the fact, and everything else is inference.
Are they in the mapped directory group? If yes and they are not in the team, synchronisation is the problem.
Are they provisioned at all? Somebody in the directory group who has never been assigned the GitHub application will not appear, and the symptom is two steps removed from the cause.
Has the mapping broken? A renamed or recreated group. Check that the team is still mapped to a group that exists.
Were they recently deprovisioned and re-provisioned? GitHub removes deprovisioned users from mapped teams even when the group still contains them, and re-adding at the IdP does not always resolve cleanly on its own.
Is synchronisation running at all? Check the audit log for recent membership events across all teams. Silence is the answer.
Resist fixing it manually. Adding them to the team in GitHub solves this person’s problem and creates a permanent divergence. Fix the cause; if the wait is unacceptable, make a temporary grant with a recorded expiry and a note explaining it.
Reconciliation
Section titled “Reconciliation”Compare both sides on a schedule. Directory group membership versus GitHub team membership, per mapped team.
Report, do not silently correct. A job that fixes differences without reporting hides the manual process producing them.
Alert on volume. One difference is a person mid-provisioning. Forty differences is a broken integration.
Check the reverse direction too. Members in a GitHub team who are not in the mapped group. Those are manual additions, and each is a hole in the model.
Look at the audit log for team membership changes made outside synchronisation. Every one has an actor and a timestamp, and the pattern tells you whether it is one person’s habit or a systemic gap.
Give the report a recipient. Divergence data with no owner is data nobody acts on.
The teams that should not be synchronised
Section titled “The teams that should not be synchronised”Not everything belongs in the directory, and pretending otherwise creates friction.
Short-lived working groups. A team formed for a two-month project does not warrant a directory group and the change control that comes with it.
On-call rotations, unless your rota system already feeds the directory. Better synchronised from the rota system directly if possible.
Teams whose membership is a GitHub-specific concept — a set of reviewers for a particular repository, for example — where no directory concept corresponds.
The rule: synchronise the teams that represent organisational reality, and leave the rest manual. But document which is which, because an unmapped team looks identical to a mapping that has broken.
Mark them. A description convention, or a naming convention, so that reconciliation can exclude them without a hand-maintained list.
Rolling out synchronisation to an existing estate
Section titled “Rolling out synchronisation to an existing estate”The awkward case: teams already exist, with membership maintained by hand, and you want to move them to the directory without losing anybody’s access.
-
Export current team membership. Every team, every member. This is your baseline and your rollback reference.
-
For each team, find or create the corresponding directory group. Where no group corresponds, that is a finding — either the team should not exist, or the organisation structure it represents is not in the directory.
-
Reconcile the memberships before mapping. Compare the GitHub team to the intended directory group and resolve every difference deliberately. Some people are in the team and should not be; some are missing.
-
Get the differences approved by the team’s manager. They are access decisions, and making them silently during a technical migration is how somebody loses access they needed.
-
Map one team first. Confirm membership matches after synchronisation, and that nothing was removed unexpectedly.
-
Then map in batches, reconciling after each.
-
Stop manual changes, and announce that clearly. This is the behavioural change, and it needs stating rather than implying.
-
Watch the reconciliation report for the first few months. Manual changes will continue for a while out of habit.
Step 3 is the valuable one. Comparing hand-maintained team membership against the directory is the first honest look most organisations get at who actually has access, and it reliably finds people who left, people who moved teams years ago, and machine accounts nobody could explain.
Expect resistance at step 7. Somebody who could previously add a colleague to a team in ten seconds now has to raise a directory request. That friction is real and it is the price of the access decision being recorded. Make the directory path fast, or the friction will be routed around.
What synchronisation does not cover
Section titled “What synchronisation does not cover”The same shape as elsewhere in this cluster, and worth restating because the gaps are where residual access lives.
Outside collaborators. Not team members, not provisioned, not synchronised.
Direct repository grants to individuals. A person granted Write on a repository, rather than through a team, is unaffected by any team change. These are the grants that survive a role change.
Organisation owners. Frequently maintained by hand, and frequently stale.
Repository administrators granted individually. Same problem, higher stakes.
Machine accounts in teams. A bot account in a team gets the team’s access and is not managed by the directory. Better handled as a GitHub App with explicit installation permissions.
Apps and their installations. An app’s access is its installation permissions and has nothing to do with teams.
Audit for direct grants specifically. They are the category that most undermines a team-based model, they are easy to enumerate through the API, and every one found is either a grant to convert into a team membership or a grant to remove.
Common mistakes
Section titled “Common mistakes”Mapping groups that were never maintained. You inherit their staleness immediately.
A group per repository. Unmaintainable at any scale.
Encoding permissions rather than facts. The directory becomes an authorisation system it was not designed to be.
Not telling the directory team. They rename and delete groups without knowing anything depends on them.
Assuming nested group behaviour. It differs by provider; test it.
Manual additions after rollout. Each one is invisible access.
Silent reconciliation. Hides the manual process instead of surfacing it.
Not distinguishing unmapped teams from broken mappings. They look the same.
No alert when synchronisation stops. The failure is silent by nature.
The organisational side
Section titled “The organisational side”Synchronisation moves a decision from one team to another, and the receiving team needs to know.
Directory group membership becomes an access decision. Whoever adds people to groups is now granting repository access, and they may not have understood that when the process was about mailing lists.
Tell them. Explicitly, in writing, with the list of groups that now drive GitHub access and what each one grants. This conversation is frequently skipped and it is the source of the “somebody added the whole department to the group” incident.
Group ownership should be clear. Each mapped group needs a named owner who approves membership changes — usually the team’s manager. Groups owned by “IT” in practice mean nobody reviews.
Request turnaround matters. If adding somebody to a group takes three days, teams will find another way to grant access, and the model erodes. A same-day path is what makes the model stick.
Include GitHub in the access review. Once teams derive from groups, reviewing groups reviews GitHub. That is the payoff, and it only materialises if somebody connects the two — the access review process needs to know that these groups now cover source code access.
And handle the exception path. There will be cases where somebody needs access urgently and the directory path is too slow. Define what happens then — a temporary manual grant with a recorded expiry is far better than an undocumented one that persists.
Measuring it
Section titled “Measuring it”Four numbers that tell you whether synchronisation is working.
Mapped teams as a proportion of all teams. Unmapped teams should be deliberate and few.
Divergence count. Members in a team but not the group, and vice versa. Should trend to zero.
Manual team membership changes per month, from the audit log. Should trend to zero after rollout; a persistent rate means the directory path is too slow.
Direct repository grants to individuals. The metric for whether teams are actually how access is granted. Should shrink over time.
Track them monthly and publish the trend. These numbers move slowly and the direction is the signal — a divergence count that stopped falling means something changed, and finding out what is easier the same month than a year later.
Mental model
Section titled “Mental model”Team membership is access. Synchronising it from the directory moves the access decision to where employment decisions already happen, which is what makes joiners, movers and leavers work without anybody remembering. The mechanism is simple; the group design is the work, and reconciliation is what makes it trustworthy.
What you learned
Section titled “What you learned”- Identity provider groups map to GitHub teams and manage both team and organisation membership
- Membership flows one way: the directory decides, GitHub follows, and manual changes are drift
- Deprovisioning removes mapped team membership even when the IdP group still contains the user
- Directory groups should express durable organisational facts, not permissions
- A group per repository does not scale; a group per team does
- Nested group behaviour depends on the identity provider and must be tested
- Group renames and deletions break mappings, often silently
- Reconciliation should report differences in both directions rather than correcting them silently
- Some teams should stay unmapped, and they must be marked so they are distinguishable from broken mappings
Exercise
Section titled “Exercise”Use an organisation with team synchronisation configured. Every step is read-only inspection — no production credentials and no changes to any live configuration.
-
List every GitHub team and whether it is mapped to a directory group.
-
For three mapped teams, compare both memberships exactly. Predict: any differences?
-
For each difference, determine the cause: manual addition, unprovisioned user, or synchronisation failure.
-
Find any team member who is not in the mapped group. How were they added, and when?
-
Check whether the directory team knows which groups drive GitHub access.
-
Test nested group behaviour with a test user.
-
Identify teams that should stay unmapped. Is there a way to tell them apart from broken mappings?
-
Design the reconciliation report you would want, and name its recipient.