Budibase/budibase · error

Replacing members is not allowed

Error message

Replacing members is not allowed

What it means

SCIM group member updates support "add" and "remove" operations only. When a PATCH operation has op "replace" (or "Replace") targeting members, update throws this error because replacing the entire member list is not a supported mutation in this implementation. The switch then falls through to utils.unreachable for unknown ops.

Source

Thrown at packages/worker/src/api/controllers/global/scim/groups.ts:136

          for (const u of value) {
            usersToAdd.push(await scimUsers.find(u.value))
          }
          break
        case "remove":
        case "Remove":
          for (const u of value) {
            try {
              usersToRemove.push(await scimUsers.find(u.value))
            } catch (e: any) {
              if (e.status !== 404) {
                throw e
              }
            }
          }
          break
        case "replace":
        case "Replace":
          throw new Error("Replacing members is not allowed")
        default:
          utils.unreachable(op)
      }
    }

    if (usersToAdd.length) {
      await groups.addUsers(
        id,
        usersToAdd.map(u => u._id!)
      )
    }
    if (usersToRemove.length) {
      await groups.removeUsers(
        id,
        usersToRemove.map(u => u._id!)
      )
    }
  }

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Reconfigure the identity provider SCIM app to send incremental add/remove member operations instead of full-membership replace
  2. Translate replace operations on the client into explicit add and remove operations per member
  3. If you control the server and need replace support, implement it by diffing current vs desired members and applying add/remove
  4. Check the IdP's SCIM connector settings (e.g. Okta 'Group Member' push mode) for a delta-sync option

Example fix

// before (client PATCH payload)
{ "Operations": [{ "op": "replace", "path": "members", "value": [...] }] }
// after
{ "Operations": [
  { "op": "add", "path": "members", "value": [{ "value": "userId1" }] },
  { "op": "remove", "path": "members[value eq \"userId2\"]" }
] }
Defensive patterns

Strategy: validation

Validate before calling

const ops = patchBody.Operations || []
const hasReplace = ops.some(o => String(o.op).toLowerCase() === "replace" &&
  (!o.path || o.path.startsWith("members")))
if (hasReplace) throw new Error("Convert replace member ops to add/remove before sending")

Type guard

function isMemberReplaceOp(op: { op: string; path?: string }): boolean {
  return op.op.toLowerCase() === "replace" && (!op.path || op.path === "members")
}

Try / catch

try {
  await scim.patchGroup(groupId, body)
} catch (e) {
  if (e.message.includes("Replacing members")) {
    // recompute diff and resend as add/remove operations
  }
}

Prevention

When it happens

Trigger: Sending a SCIM PATCH /Groups request whose Operations array contains {"op": "replace", "path": "members", ...} — typically from an SCIM client (Okta, Azure AD/Entra) configured to sync full group membership instead of incremental adds/removes.

Common situations: An identity provider SCIM integration set to "sync all members" mode sends replace ops; a client application builds PATCH payloads with replace instead of add/remove; SCIM spec-conformance tooling tests replace semantics.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/8ddd8c0e2438fc41. Report an issue: GitHub.