@codex review
Paired with #516. Together they close MCPG-193.
FusionAuth's default SCIM Group Request Converter does not stamp the SCIM source marker (group.data.source = 'scim') that admin webhook ingestion needs to distinguish IdP-pushed groups from manually-created FA groups. Without that marker, webhookPayloadHasScimSourceMarker silently rejects every Okta-pushed group webhook — every tenant on FA's default group request converter was affected before this. Only planktoncomposio had a hand-wired custom lambda from the original MCPG-193 investigation; this PR makes the equivalent lambda the standard for every tenant.
Without this PR, even with #516 merged, push groups still only works on planktoncomposio. Other tenants need either manual lambda wiring or this PR.
| PR | What it fixes | Where it lives |
|---|---|---|
| #516 | The 500 inside applyScimProjectionPlan (syncProjectedOrgAdminRegistrations over-broad scope) | apps/admin/server/scim/project.ts |
| This PR | The source-scope marker gate (default FA lambda doesn't stamp data.source = 'scim') | apps/admin/server/fusionauth-scim-group-lambdas.ts + fusionauth-scim-tenant-config.ts |
The two are independent code paths that happen to gate the same end-to-end flow. Splitting them keeps each PR small and reviewable.
New module apps/admin/server/fusionauth-scim-group-lambdas.ts. Mirrors the user-lambda pattern introduced by MCPG-209 PR #511. Exports:
COMPOSIO_SCIM_GROUP_REQUEST_CONVERTER_ID — deterministic UUID derived from a fixed namespace + name. Same lambda survives provisioning re-runs and is safe to backfill.COMPOSIO_SCIM_GROUP_REQUEST_CONVERTER_BODY — the request converter body verified working against planktoncomposio during the original MCPG-193 investigation. Stamps group.data.source = 'scim', copies scimGroup.externalId if present, sets group name from scimGroup.displayName, and copies members.ensureComposioScimGroupLambda() — GET the lambda by id; if 404, POST to create; if present but body/name/type/enabled drift, PUT to repair. Idempotent.resolveStockScimLambdas in fusionauth-scim-tenant-config.ts now calls ensureComposioScimGroupLambda() for the group request converter slot. The other three converter types (user request, user response, group response) still resolve via /api/lambda/search — group response is intentionally left to FA's default since Composio→Okta sync is not the priority direction and was never broken.
fusionauth-scim-group-lambdas.test.ts. Covers:
FUSIONAUTH_URL / FUSIONAUTH_API_KEY are missingfusionauth-scim-tenant-config.test.ts. Mocks the new ensure function; rewrites the "resolves all stock lambdas" assertion to verify only 3 search calls (not 4) and that scimGroupRequestConverterId comes from the upsert.bunx turbo check-types --filter=@repo/admin — greenbunx turbo lint --filter=@repo/admin — greenmain, unrelated)planktoncomposio) still have their lambdaConfiguration.scimGroupRequestConverterId pointing at FA's default. After this PR ships, future provisioning will wire the Composio-managed id. A backfill (script that PATCHes tenant.lambdaConfiguration to repoint at the managed id) should be a follow-up — either folded into MCPG-209 PR #512 or filed separately./api/lambda/search and uses FA's default. Composio→Okta sync is not the priority direction and was never broken; baking the response converter would only be useful if/when that direction becomes a customer requirement.a00a6486-958f-4d7e-bf1b-36c34842068c). It becomes orphaned but harmless once the tenant gets repointed by backfill. Sweepable later.@codex review
@codex review
Codex Review: Didn't find any major issues. You're on a roll.
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".
Rebased onto mcpg-209-bug1-known-good-scim-user-lambdas (#511's branch) and changed PR base accordingly. This converts #517 into a stacked PR on top of #511.
Why: Both #511 and #517 modify apps/admin/server/fusionauth-scim-tenant-config.ts and its test (each replacing different stock-lambda searches with upserted Composio-managed lambdas). Without this rebase, whichever PR landed second would force a merge-resolution at land-time.
What changed in this rebase:
ensureComposioScimUserLambdas from #511 + ensureComposioScimGroupLambda from #517) into a single Promise.all.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.
STOCK_SCIM_LAMBDA_TYPESSCIMServerGroupResponseConverterresolveStockScimLambdas to take the single remaining stock-search result directly instead of going through the now-unnecessary findLambdaId helper.beforeEach, added a "propagates failures from the Composio group lambda upsert" test, repointed stock-search test scenarios (ambiguous / prefers custom / refuses mismatched type) to SCIMServerGroupResponseConverter since that's now the only searched type.All prior Codex P1/P2 feedback in earlier review threads (engineType, race handling, drift-gate hardening) remains addressed in code — those threads will appear as "Outdated" since line numbers shifted, but the substance is preserved across the 3 commits on top of #511.
Merge order: land #511 first. Then this PR's effective diff against main becomes just its own 3 commits (#511's commits will be deduplicated by the merge).
CI / Codex will re-evaluate on the new commits.
@codex review
Codex Review: Didn't find any major issues. Hooray!
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".
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 22, 2026 8:08pm | |
| composio-enterprise-internal | May 22, 2026 8:08pm |
Deployment failed with the following error:
You must set up Two-Factor Authentication before accessing this team.
View Documentation: https://vercel.com/docs/two-factor-authentication