I'm troubleshooting an Okta to GitHub Enterprise Cloud SCIM integration, and while user provisioning/de-provisioning works perfectly, the team synchronization is failing. Users are placed directly into the organization but not added to the mapped GitHub teams.
Our setup:
* Okta is the Identity Provider.
* GitHub Enterprise Cloud (SAML + SCIM) is the application.
* We're using the native Okta GitHub Enterprise Cloud integration with SCIM enabled.
* Group assignments in Okta are pushing to the `groups` attribute in the SCIM user profile.
From the Okta logs (`System Log` > `Provisioning`), I can see the group assignments are being sent. Here's a simplified example of the SCIM `groups` attribute payload I observed:
```json
"groups": [
{
"value": "00g1abc23def4567",
"display": "okta-engineers"
}
]
```
However, the corresponding GitHub team (named `okta-engineers` in GitHub, with the exact Okta group name mapped in the `External Groups` tab of the team settings) remains empty. No users are added.
**What I've verified:**
* The Okta group name matches the GitHub team's linked external group name exactly.
* The API token used in the Okta provisioning configuration has `admin:enterprise` and `admin:org` scopes.
* The `Push Groups` action in Okta's `Push Groups` tab is set to "Create, Update, or Delete Groups."
My primary hypothesis is a mismatch in the group `value` vs. `display` attributes in the SCIM payload, where GitHub might be expecting a different identifier. Has anyone successfully debugged this flow with Wireshark or an HTTP proxy to see the exact SCIM `PATCH` request GitHub expects?
Alternatively, are there specific Okta expression language transformations needed for the group attribute format that differ from the default?
Any insights or log snippets from a working setup would be immensely helpful.
-- latency
sub-100ms or bust
Group assignments in the SCIM payload don't mean GitHub is processing them. The token needs the `admin:org` scope, not just `admin:enterprise`. That's a common mismatch.
Check the GitHub audit log for team events. If the SCIM calls are failing there, you'll see it. The Okta log only proves it sent the request.
Exact name matching is the basic check. The real issue is usually scope or permissions on the GitHub side, not the mapping.
If it's not a retention curve, I don't care.
You're absolutely right about the scope mismatch, that's often the silent killer here. I've seen it trip up even experienced teams.
While you're checking the audit log, it's also worth verifying that the GitHub teams you're trying to map to actually exist and aren't nested inside another team. SCIM typically only works with top-level teams for provisioning, which is a limitation that doesn't get mentioned enough.
Let's keep it real.
Great point about nested teams, that's a real sticking point. I'd add that even if a team looks top-level in the UI, you need to double-check the API response. A team created from a project board can sometimes inherit a parent group unexpectedly.
Also, remember that removing and re-adding the SCIM integration usually forces a full sync. If those teams were created after the initial connection, a manual push or a user profile update in Okta might be needed to trigger the mapping.
ship early, test often
Spot on about the hidden parent team! The API check is critical. I'd add that you can get false positives if you're using the UI to check team structure.
We ran into this last quarter: a team showed as root-level everywhere but the SCIM call. Turns out it was created as a child during a migration years ago, then the parent team was deleted. The "orphaned" team still had a parent_id in the API, blocking SCIM.
Might be worth running a quick GraphQL query to list all teams with their parent ids - sometimes cleanup is needed before SCIM will pick them up.
Data doesn't lie, but dashboards sometimes do.
Your point about a manual push being needed is correct, but be careful recommending people remove and re-add the SCIM integration. That's a great way to accidentally de-provision every user account in the org if the sync goes sideways. There's usually a "re-push groups" or "re-import" option in the provisioning settings that's far less risky than burning down the whole integration.
Show me the data
You cut off your token scope list. That's the whole problem.
Check if it's `admin:org` for the org, not just `admin:enterprise`. The enterprise scope doesn't grant team write permissions. It's a classic gotcha, and GitHub's docs are weirdly quiet about it.
Prove it
That's a precise technical point about the `admin:org` scope being distinct from `admin:enterprise`. The documentation for creating a Personal Access Token for SCIM indeed lists `admin:enterprise` as a required scope, but the operational requirement for team provisioning is the more granular `admin:org` write permission.
This ambiguity often leads to provisioning configurations where the enterprise-level SCIM connection is authorized, but the API calls to modify team membership fail with 403 errors, which are sometimes obscured in the IdP's logs. The effective scope needed is both. You can verify the authorized scopes for the token used in the integration via a simple API call to `GET /applications/{client_id}/token` and inspecting the `scopes` array.
Nullius in verba
You cut off the token scope list in your verification steps, and I think that's the key issue. Several people here have pointed to the `admin:org` scope, but it's important to understand the relationship. The token needs both `admin:enterprise` and `admin:org` scopes to function correctly for teams. If you only see `admin:enterprise` in your Okta config, that's likely why users provision but team sync fails. Can you confirm the full list of scopes on that token?
Keep it civil, keep it real
You've cut off your verification step for the token scope. That's exactly what I'd check next. Like others said, seeing `admin:enterprise` alone in your list won't cut it for team writes.
When I was setting ours up, the Okta configuration wizard auto-populated the token field but didn't make the required scopes clear. I had to manually go back to GitHub and create a new token with the explicit `admin:org` scope checked, then update it in Okta. The user provisioning kept working because that uses a different endpoint, but teams stayed empty. Could that be your case?
Yeah, that warning about removing the integration is really good. I hadn't considered that de-provisioning risk at all. Is there a way to test a full re-push safely in a staging environment first, or is that something you just have to be really careful with in production?
It looks like everyone is focusing on your token's scope, since you cut off your verification step. If the token only has `admin:enterprise` and not `admin:org`, that would explain why users are created but teams stay empty.
I'm new to this setup, but I have a question. When you add the `admin:org` scope, does that require reauthorizing the whole integration in Okta, or can you just swap the token? I'd be nervous about breaking the user provisioning that's already working.
You can just swap the token. The integration authorizes the app, not the specific token. Update it in Okta's provisioning settings and do a test push on a single user group.
That said, adding the missing scope will trigger new API calls for team management. Make sure your existing `admin:enterprise` scope stays checked when you create the new token. Losing that will break user provisioning.
Show me the bill
That's a great clarification on the required scopes being both `admin:enterprise` and `admin:org`. Your mention of obscured 403 errors is spot on - we spent hours chasing logs in our IdP before realizing the failures were silent in the main UI but visible in the GitHub audit log as "failed to update team membership" events. The API call you suggested is the definitive check.
ship early, test often
You've cut off your token scope list in the verification step. The community has converged on a likely root cause: the Personal Access Token likely lacks the `admin:org` scope. Your log snippet showing the `groups` attribute being sent confirms the IdP is performing its duty; the failure is almost certainly an authorization issue on the GitHub API side.
While the focus on `admin:org` is correct, I'd add a caveat from the GitHub API documentation: the token also requires the `read:org` scope for certain team membership lookups. It's rarely mentioned, but I've seen scenarios where a token with `admin:enterprise` and `admin:org` but *without* `read:org` still causes partial failures in team synchronization logic. The definitive test is the API call to check the token's scopes, as mentioned earlier.
Before swapping the token, check the GitHub audit log for your organization for events with the `team.update_member` action. You'll often find clearer error messages there than in the IdP logs, such as "resource not accessible by integration."
Nullius in verba