odysseus-dev/odysseus · error · HTTPException
Cannot rename user
Error message
Cannot rename user
What it means
Raised by PUT /users/{username}/rename when auth_manager.rename_user() returns False after the route's own pre-checks passed. Looking at core/auth.py:341, the remaining failure causes are: new_username is in RESERVED_USERNAMES (the route does NOT pre-check this), the requesting user lost admin between the route gate and the manager call, or old/new vanished in a race. Note the route uses 400 here, though reserved-name is conceptually the same case as error 220's 403.
Source
Thrown at routes/auth_routes.py:330
raise HTTPException(403, "Admin only")
old_username = (username or "").strip().lower()
new_username = (body.username or "").strip().lower()
if not new_username:
raise HTTPException(400, "Username required")
if old_username == new_username:
return {"ok": True, "username": new_username, "renamed_self": old_username == user}
if old_username not in auth_manager.users:
raise HTTPException(404, "User not found")
if new_username in auth_manager.users:
raise HTTPException(409, "Username already taken")
# Gate on auth first. Every mutation below is contingent on this
# succeeding — doing it last meant a rejected rename (e.g. reserved
# username) left file-backed owner fields already rewritten with no
# way to roll them back.
ok = auth_manager.rename_user(old_username, new_username, user)
if not ok:
raise HTTPException(400, "Cannot rename user")
def _rollback_auth_rename() -> bool:
# On self-rename the admin session has already moved to the new
# username, so the rollback must authenticate as the new user.
rollback_user = new_username if user == old_username else user
try:
return bool(auth_manager.rename_user(new_username, old_username, rollback_user))
except Exception as rollback_err:
logger.error(
"Failed to roll back auth rename %s -> %s after owner migration failure: %s",
new_username, old_username, rollback_err,
)
return False
# Usernames are ownership keys for user data. Rename the common
# owner-scoped DB rows so the account keeps access to its sessions,
# docs, email accounts, tasks, etc.
try:View on GitHub (pinned to f9235ebbf1)
Solutions
- Avoid the reserved names {internal tool user, api, demo, system} (any case) as the new username.
- Re-fetch GET /users, confirm your own admin status and the target's existence, then retry with a non-reserved name.
- If you must claim a reserved-looking name, choose a variant like 'svc-system'.
Example fix
// before
await api.put(`/users/${old}/rename`, { username: 'api' });
// after
const RESERVED = ['system', 'api', 'demo']; // + internal tool user
const next = RESERVED.includes(newName.toLowerCase()) ? `user-${newName}` : newName;
await api.put(`/users/${old}/rename`, { username: next }); Defensive patterns
Strategy: validation
Validate before calling
RESERVED = {'system', 'api', 'demo'} # + internal tool user
if new.strip().lower() in RESERVED:
raise ValueError('target username is reserved') Try / catch
try:
put(f'/users/{old}/rename', {'username': new})
except HTTPError as e:
if e.response.status_code == 400 and 'Cannot rename' in e.response.json()['detail']:
validate_name_not_reserved_and_retry_once() # else surface error
raise Prevention
- The route does NOT pre-check reserved names — the client must
- Reuse one shared reserved-list constant across create and rename flows
When it happens
Trigger: Renaming a user to 'system', 'api', 'demo', or the internal tool user name (most common cause); requesting admin demoted concurrently; target user deleted concurrently after the route's existence check.
Common situations: Renaming service-style accounts onto reserved names during cleanup; admins acting simultaneously from two sessions.
Related errors
- Username required
- Username already taken
- Invalid credentials
- Not authenticated
- Current password is incorrect
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/1d7444052167df3e.
Report an issue: GitHub.