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

  1. Avoid the reserved names {internal tool user, api, demo, system} (any case) as the new username.
  2. Re-fetch GET /users, confirm your own admin status and the target's existence, then retry with a non-reserved name.
  3. 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

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


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/1d7444052167df3e. Report an issue: GitHub.