affaan-m/ECC · error · HTTPException
User not found
Error message
User not found
What it means
In `update_user`, after `UserService.update` returns without raising, the handler checks `if user is None` and raises `HTTPException(404, "User not found")`. The service returns `None` (rather than raising) to signal the target row did not exist. Note the ownership check at 586 runs first, so a non-owner always gets 403 even if the id does not exist.
Source
Thrown at skills/fastapi-patterns/SKILL.md:283
return UserListResponse(total=total, items=users)
@router.patch("/{user_id}", response_model=UserResponse)
async def update_user(
user_id: int,
payload: UserUpdate,
db: DbDep,
current_user: ActiveUserDep,
) -> UserResponse:
if current_user.id != user_id:
raise HTTPException(status_code=403, detail="Not authorized")
service = UserService(db)
try:
user = await service.update(user_id, payload)
except DuplicateUserError:
raise HTTPException(status_code=400, detail="Email already registered")
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.post("/token")
async def login(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
db: DbDep,
) -> dict[str, str]:
service = UserService(db)
token = await service.authenticate(form_data.username, form_data.password)
if token is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return {"access_token": token, "token_type": "bearer"}
```View on GitHub (pinned to 01e15490f0)
Solutions
- Front-end: on 404 with this detail, force logout and redirect to login — the JWT references a non-existent user.
- Have `UserService.update` raise a domain `UserNotFoundError` and map it here, for symmetry with `DuplicateUserError`.
- If soft-deleted users should be recoverable, return 410 Gone instead and surface a restore flow.
- Verify the id source: the JWT `sub` claim should match the row.
Example fix
# before
if user is None:
raise HTTPException(status_code=404, detail="User not found")
# after — let the service raise, map consistently
from app.services.user_service import UserNotFoundError
try:
user = await service.update(user_id, payload)
except UserNotFoundError:
raise HTTPException(status_code=404, detail="User not found") Defensive patterns
Strategy: try-catch
Validate before calling
null
Type guard
null
Try / catch
from fastapi import HTTPException
try:
updated = await client.patch(f'/users/{uid}', json=payload)
except HTTPException as e:
if e.status_code == 404:
logout_and_redirect_login()
raise Prevention
- Have the service raise `UserNotFoundError`; map it consistently across routes.
- On 404, treat the JWT as stale: force logout.
- Verify the JWT `sub` matches a live row before issuing long-lived tokens.
When it happens
Trigger: Authenticated user patches their own id, but the row was deleted between token issuance and the PATCH — `UserService.update` finds nothing and returns `None`. Also if the client sends an id the user owns (per JWT) but the DB was truncated.
Common situations: Account deleted (soft or hard) while session was active. Test DB cleaned mid-run. Off-by-one in id construction.
Related errors
- Email already registered
- Passwords do not match
- Inactive user
- Not authorized
- Incorrect username or password
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/a90ff2346dc491e1.
Report an issue: GitHub.