odysseus-dev/odysseus · error · HTTPException
Username required
Error message
Username required
What it means
Raised by PUT /users/{username}/rename when body.username (RenameUserRequest) is empty after strip().lower(). The path username is not validated for emptiness here — only the NEW name is, because the 'old not in users' check below covers a bogus old name with 404.
Source
Thrown at routes/auth_routes.py:316
async def update_user_privileges(username: str, request: Request):
user = _get_current_user(request)
if not user or not auth_manager.is_admin(user):
raise HTTPException(403, "Admin only")
body = await request.json()
ok = auth_manager.set_privileges(username, body)
if not ok:
raise HTTPException(404, "User not found or is admin")
return {"ok": True, "privileges": auth_manager.get_privileges(username)}
@router.put("/users/{username}/rename")
async def rename_user(username: str, body: RenameUserRequest, request: Request):
user = _get_current_user(request)
if not user or not auth_manager.is_admin(user):
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.View on GitHub (pinned to f9235ebbf1)
Solutions
- Send a non-empty, non-whitespace username in the body field the RenameUserRequest model expects (username).
- Trim client-side before submit and disable the submit button for empty input.
- Confirm the request model field name against the OpenAPI schema (GET /openapi.json) if the field seems populated.
Example fix
// before
await api.put(`/users/${old}/rename`, { new_username: next });
// after
await api.put(`/users/${old}/rename`, { username: next.trim() }); Defensive patterns
Strategy: validation
Validate before calling
new_name = (payload.get('username') or '').strip()
if not new_name:
raise ValueError('username field is required and must be non-empty') Type guard
def is_valid_rename_body(body: dict) -> bool:
return isinstance(body.get('username'), str) and bool(body['username'].strip()) Try / catch
try:
put(f'/users/{old}/rename', {'username': new})
except HTTPError as e:
if e.response.status_code == 400 and 'required' in e.response.json()['detail']:
fix_payload_field() # wrong/empty field — never retry unchanged
raise Prevention
- Trim input and block empty submits client-side
- Confirm the body field name against the OpenAPI schema
When it happens
Trigger: POSTing {"username": ""} or {"username": " "} in the JSON body; client sending {"new_username": ...} (wrong field name) so the expected field deserializes as empty/None.
Common situations: Form submitted with an empty rename field; mismatch between API docs and client model (new_username vs username); whitespace-only input from copy-paste.
Related errors
- Cannot rename user
- Invalid remote_host — must be host or user@host, no SSH opti
- Invalid ssh_port
- Unknown wipe kind: {kind!r}
- Username already taken
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/17907620b6514b2b.
Report an issue: GitHub.