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
- On 409, treat as success if the goal is 'user has this role' — fetch assignments and verify
- Make the client idempotent: GET the assignment list first and skip if the tuple already exists
- Disable the submit button / debounce to prevent double posts
- 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
- Make assignment creation idempotent by checking for an existing tuple first
- Debounce submit buttons to prevent double posts
- On network timeout, verify with a GET before retrying the POST
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
- Role with name {payload.name!r} already exists
- Superuser required to administer role assignments.
- user_id not found
- role_id not found
- Assignment not found
AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14).
Data as JSON: /api/errors/9d7566fef7f9b2e2.
Report an issue: GitHub.