apache/superset · error · ValidationError
A rule with this name already exists.
Error message
A rule with this name already exists.
What it means
Raised by UpdateRLSRuleCommand.run when RLSDAO.update + db.session.flush() hits an IntegrityError on the rule-name unique constraint. The preflight validate_uniqueness in validate() is not atomic with the update, so concurrent renames can collide here; the constraint error is translated into a ValidationError on 'name'.
Source
Thrown at superset/commands/security/update.py:59
def __init__(self, model_id: int, data: dict[str, Any]):
self._model_id = model_id
self._properties = data.copy()
self._tables = self._properties.get("tables", [])
self._subjects = self._properties.get("subjects", [])
self._model: Optional[RowLevelSecurityFilter] = None
@transaction()
def run(self) -> Any:
self.validate()
assert self._model
try:
updated_model = RLSDAO.update(self._model, self._properties)
db.session.flush()
except IntegrityError as ex:
# The preflight uniqueness check in ``validate`` isn't atomic with
# this update, so fall back to the database's unique constraint
# and translate it into the same descriptive validation error.
raise ValidationError(
{"name": [_("A rule with this name already exists.")]}
) from ex
return updated_model
def validate(self) -> None:
self._model = RLSDAO.find_by_id(int(self._model_id))
if not self._model:
raise RLSRuleNotFoundError()
# Datasource access is validated before revealing whether the
# requested name is already in use, so an unauthorized caller can't
# use the duplicate-name response to enumerate rule names.
if "tables" in self._properties:
tables = (
db.session.query(SqlaTable)
.filter(SqlaTable.id.in_(self._tables)) # type: ignore[attr-defined]
.all()
)View on GitHub (pinned to f4587218dd)
Solutions
- On this 400, GET the rules list and reconcile: either pick a different name or update the other rule that now owns the name
- Retry the PUT with a distinct name
- Serialize rule renames through a single writer to avoid the race
Example fix
# before
client.put(f'/api/v1/rowlevelsecurity/{rid}', json={'name': 'tenant_filter'})
# after
name = 'tenant_filter'
clash = client.get('/api/v1/rowlevelsecurity/?q=(name:eq:tenant_filter)').json()['count']
if clash:
name = 'tenant_filter_v2'
client.put(f'/api/v1/rowlevelsecurity/{rid}', json={'name': name}) Defensive patterns
Strategy: try-catch
Validate before calling
taken = any(r['name'] == new_name and r['id'] != rid
for r in client.get('/api/v1/rowlevelsecurity/').json()['result'])
assert not taken Try / catch
from superset.commands.exceptions import ValidationError
try:
UpdateRLSRuleCommand(rid, props).run()
except ValidationError as e:
if 'name' in e.normalized_messages():
props['name'] = uniquify(props['name']); UpdateRLSRuleCommand(rid, props).run()
else:
raise Prevention
- Check target-name availability (excluding own id) before rename
- Serialize rule renames through one writer
- Reconcile instead of blind-retrying after a timeout
When it happens
Trigger: PUT /api/v1/rowlevelsecurity/{id} renaming a rule to a name another session created (or renamed to) between the preflight check and the flush; two concurrent PUTs converging on the same name.
Common situations: Parallel automation renaming RLS rules; retry-after-timeout logic that re-sends a rename which another attempt already applied.
Related errors
- Job not found or already completed
- Annotation not found.
- Datasource does not exist
- Regular RLS filters require at least one subject.
- Datasource does not exist
AI-assisted analysis of apache/superset@f4587218dd (2026-08-14).
Data as JSON: /api/errors/ea34a2e93c8a1670.
Report an issue: GitHub.