langflow-ai/langflow · warning · HTTPException

Assignment already exists for this user/role/domain

Error message

Assignment already exists for this user/role/domain

What it means

Raised by POST /api/v1/authz/role-assignments when session.commit() raises SQLAlchemy IntegrityError, almost always the unique constraint on (user_id, role_id, domain_type, domain_id). The route rolls back and returns HTTP 409 'Assignment already exists for this user/role/domain'.

Source

Thrown at src/backend/base/langflow/api/v1/authz_role_assignments.py:114

        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user_id not found")
    role = await session.get(AuthzRole, payload.role_id)
    if role is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="role_id not found")

    assignment = AuthzRoleAssignment(
        user_id=payload.user_id,
        role_id=payload.role_id,
        domain_type=payload.domain_type,
        domain_id=payload.domain_id,
        assigned_at=datetime.now(timezone.utc),
        assigned_by=current_user.id,
    )
    session.add(assignment)
    try:
        await session.commit()
    except IntegrityError as exc:
        await session.rollback()
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Assignment already exists for this user/role/domain",
        ) from exc
    await session.refresh(assignment)
    await safe_invalidate_user(
        get_authorization_service(),
        payload.user_id,
        op="role_assignment:create",
    )
    await audit_decision(
        user_id=current_user.id,
        action="role_assignment:create",
        obj=f"user:{payload.user_id}",
        result="allow",
        details={
            "assignment_id": str(assignment.id),
            "role_id": str(payload.role_id),
            "role_name": role.name,

View on GitHub (pinned to 976ec789d2)

Solutions

  1. On 409, treat as success if the goal is 'user has this role' — fetch assignments and verify
  2. Make the client idempotent: GET the assignment list first and skip if the tuple already exists
  3. Disable the submit button / debounce to prevent double posts
  4. For concurrent provisioning, catch 409 explicitly instead of failing the batch

Example fix

// before
await api.post('/authz/role-assignments', payload);

// after
try {
  await api.post('/authz/role-assignments', payload);
} catch (e) {
  if (e.status !== 409) throw e; // 409 = already assigned, desired end state
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await api.get('/authz/role-assignments', { params: { user_id, role_id } });
const dup = existing.some(a => a.user_id === user_id && a.role_id === role_id && a.domain_type === domain_type && a.domain_id === domain_id);
if (dup) return;

Try / catch

try { await createAssignment(payload); }
catch (e) { if (e.status !== 409) throw e; /* already assigned = goal state */ }

Prevention

When it happens

Trigger: POSTing the same user/role/domain combination twice — e.g. double-submit, retry after a network timeout where the first request actually committed, or two admins assigning the same role concurrently.

Common situations: Non-idempotent retry logic in clients, race between UI tabs, or provisioning scripts run twice without existence checks. The unique constraint at the DB level is the final guard; the API converts it to a clean 409.

Related errors


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/9d7566fef7f9b2e2. Report an issue: GitHub.