@codex review
Closes MCPG-193.
Okta group push webhooks fail with HTTP 500 (group_update_processing_failed) on every tenant whose org_admins were set up outside SCIM (Composio onboarding, manual invite, staff provisioning). The auto-create chain inside ensureScimManagedTeamForWebhookGroup invokes applyScimProjectionPlan, which in turn calls syncProjectedOrgAdminRegistrations with an over-broad scope — it treats every current FA org_admin as a SCIM-managed admin and refuses to operate when the SCIM-desired set is empty, throwing SCIM projection cannot remove the last organization admin.
This PR scopes the projection's org_admin management to users SCIM owns (entries in scim_users for the org). Non-SCIM admins are now left untouched regardless of the SCIM-desired set.
Before:
futureOrgAdminCount = totalCurrentOrgAdminCount // ALL admins in FA
- scopedCurrentOrgAdminCount // ALL admins in FA (over-broad)
+ grants // SCIM-derived grants
For an org with no SCIM rules and no SCIM users: N − N + 0 = 0 → throws.
After:
futureOrgAdminCount = totalCurrentOrgAdminCount // unchanged
- SCIM-managed admins to be revoked // only SCIM scope
+ grants // unchanged
Same org: N − 0 + 0 = N → no throw, no revocations, no grants.
Org where SCIM legitimately wants to remove the last SCIM-owned admin: still throws. Safety preserved.
syncProjectedOrgAdminRegistrations gains scimManagedFusionauthUserIds: string[] and filters currentOrgAdmins through it.buildScimProjectionPlanWithState derives the list from state.users (no new DB query — loadScimProjectionState already pulls every SCIM user for the org) and returns it.applyScimProjectionPlan threads the value through.Triggered the actual auto-create chain locally against the planktoncomposio prod tenant before and after the fix:
| State | Result |
|---|---|
| Before (clean main) | AppError: SCIM projection cannot remove the last organization admin. at project.ts:591 |
| After (this branch) | status: "processed" — team auto-created end-to-end |
Stack trace before the fix matches the ticket exactly.
leaves non-SCIM org admins untouched when SCIM has no users or rules (MCPG-193) — direct repro of MCPG-193 as a unit test.@codex review
Codex Review: Didn't find any major issues. Already looking forward to the next diff.
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
@codex review
Codex Review: Didn't find any major issues. Nice work!
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Pushing a group from Okta lands the group in FusionAuth correctly, but the resulting group.create / group.update webhook delivered to the Composio admin throws inside the team auto-create pipeline and returns HTTP 500 group_update_processing_failed. The SCIM-managed team never gets created, and Okta-originated groups never appear in Composio.
Tested live against planktoncomposio (FA tenant 1ea93b19-2f09-4b28-a71e-470f3e25a2e9, prod admin at https://egateway.composio.dev) on 2026-05-15.
| Layer | Status |
|---|---|
| Okta → FusionAuth SCIM POST | ✅ groups land (e.g. plank444 id=43ca9bc3-a75e-41ee-8725-2b6d6f513cbb) |
| FA SCIM Group Request Converter Lambda | ✅ writes data.source = "scim" onto the group (verified — see "Test setup" below) |
| FA webhook config | ✅ scoped to tenant 1ea93b19-…, URL https://egateway.composio.dev/api/fusionauth/webhooks/group-updated, all group.* events enabled, shared-secret auth |
| Composio webhook auth | ✅ secret matches (probe returns 500, not 401) |
| Composio webhook schema validation | ✅ passes (probe returns 500, not 400 invalid_payload) |
| Composio org-context resolution | ✅ passes — Organization for planktoncomposio has fusionauthTenantId = 1ea93b19-… and scimEnabled = true (probe would otherwise short-circuit to 200 silently) |
| Composio team auto-create pipeline | ❌ throws → caught by webhook route, returns 500 |
The 500 originates in the catch block at apps/admin/app/api/fusionauth/webhooks/group-updated/route.ts:207-218, which calls — the actual stack trace lives in Sentry.
Sentry.captureException(error, ...)Call path that throws:
ensureScimManagedTeamForWebhookGroup(...) is invoked because shouldCreateSourceScopedGroup = true (the source marker data.source: "scim" is present in the payload)ensureScimManagedTeamForWebhookGroup runs upsertNormalizedScimGroup then createScimManagedTeamcreateScimManagedTeam runs withRepairRollback({ work: ... }) which calls updateTeamScimBinding then applyScimProjectionPlanForRepairapplyScimProjectionPlan(orgId) — heaviest operation, hits Postgres, FA APIs, and the projection engineWhen work throws, withRepairRollback deletes the just-created Team row, so nothing remains in the DB to debug from the outside.
applyScimProjectionPlan requires settings.fusionauth.applications.admin.id and settings.fusionauth.applications.mcp.id on the Organization (project.ts:1428-1430). If those aren't configured on planktoncomposio's Org settings, downstream syncProjectedTeamMemberships / role-assignment code will throw.syncProjectedTeamMemberships calls FA group/membership APIs that may fail when the team has zero members or when the FA admin/mcp application/role config is incomplete.createScimManagedTeamWithAutoSlug — less likely since both my real-id probe and synthetic-id probe failed with identical error.We can't pick between these without the actual stack trace.
Two probes against the prod admin on 2026-05-15, both returned HTTP 500 {"error":"group_update_processing_failed"}:
data.source: "scim", externalId: "okta-diag-probe":
x-flow-id: ae065023-ccab-4874-9767-07f8f3bfda25x-request-id: req_5cfae86c-403a-4c49-8a43-5f43e1ae580143ca9bc3-a75e-41ee-8725-2b6d6f513cbb), data.source: "scim":
x-flow-id: 76bf0ab1-3355-418b-bd7b-5bbedd8a65f0x-request-id: req_b8a7c688-dc04-44da-92ce-5a667c2776fcThese should map to captured Sentry exceptions for component: "fusionauth-group-updated-webhook" with the full stack.
A. New SCIM Group Request Converter Lambda created in FusionAuth
a00a6486-958f-4d7e-bf1b-36c34842068cComposio planktoncomposio SCIM Group Request Converter (externalId)SCIMServerGroupRequestConverter// planktoncomposio-only — copies SCIM externalId into FA group.data so the Composio webhook source-scope check passes
function convert(group, members, options, scimGroup) {
group.data = group.data || {};
group.data.source = 'scim';
if (scimGroup.externalId) {
group.data.externalId = scimGroup.externalId;
}
group.name = scimGroup.displayName;
if (scimGroup.members) {
for (var i = 0; i < scimGroup.members.length; i++) {
members.push({
userId: scimGroup.members[i].value,
data: { $ref: scimGroup.members[i]['$ref'] }
});
}
}
}
B. Tenant patched to reference the new lambda
1ea93b19-2f09-4b28-a71e-470f3e25a2e9 (planktoncomposio)lambdaConfiguration.scimGroupRequestConverterId changed from fda7bc79-74a4-48c8-8681-039aceb822dd (shared FA default) to a00a6486-… (new lambda above)Revert command if we want to back out:
PATCH /api/tenant/1ea93b19-2f09-4b28-a71e-470f3e25a2e9
{ "tenant": { "lambdaConfiguration": { "scimGroupRequestConverterId": "fda7bc79-74a4-48c8-8681-039aceb822dd" } } }
Independent of the auto-create bug, the default FA SCIM Group Request Converter never copies SCIM externalId into group.data, so even before reaching the auto-create code path, the source-scope marker check at apps/admin/server/scim/source-scope.ts:31-48 would reject every Okta-originated group webhook. The new lambda fixes that gate for planktoncomposio. The remaining 500 is a separate, downstream bug.
When fixing this properly, the lambda change should either be:
fda7bc79-… body, or set up a "Composio Standard" lambda used by all SCIM tenants), orapps/admin/server/routers/onboarding/configure-scim.ts or wherever the FA tenant SCIM setup runs).1ea93b19-… from test pushes: plank444, plank222, Plank123, composio123, composio123-admins, neto123, neto123-admins, Teste, Team test, Testing, and okta-…-admins siblings.ScimGroup row from each probe (the rollback only deletes the Team row, not the upserted ScimGroup — see webhook-ingestion.ts:449-457 and withRepairRollback at repair.ts:611-628). Worth confirming and cleaning.error.message (and code) in the response body, deploy to prod, re-probe.applyScimProjectionPlan / syncProjectedTeamMemberships (or wherever the stack points).group.create auto-create path against the test DB so this can't regress silently.ScimGroup rows in prod DB.The latest updates on your projects. Learn more about Vercel for GitHub.
| Project | Deployment | Actions | Updated (UTC) |
|---|---|---|---|
| composio-enterprise-admin | Preview | May 21, 2026 3:49pm | |
| composio-enterprise-internal | May 21, 2026 3:49pm |