affaan-m/ECC · warning · ValueError

Passwords do not match

Error message

Passwords do not match

What it means

A Pydantic v2 `model_validator(mode="after")` on the `UserCreate` schema raises `ValueError` when `password != password_confirm`. Pydantic converts a validator `ValueError` into a `ValidationError` that FastAPI surfaces as HTTP 422 with a field-level error. `password_confirm` is a transport-only field and is not persisted.

Source

Thrown at skills/fastapi-patterns/SKILL.md:138

```python
# app/schemas/user.py
from datetime import datetime
from pydantic import BaseModel, EmailStr, Field, model_validator


class UserBase(BaseModel):
    email: EmailStr
    username: str = Field(min_length=3, max_length=50)


class UserCreate(UserBase):
    password: str = Field(min_length=8)
    password_confirm: str

    @model_validator(mode="after")
    def passwords_match(self) -> "UserCreate":
        if self.password != self.password_confirm:
            raise ValueError("Passwords do not match")
        return self


class UserUpdate(BaseModel):
    username: str | None = Field(default=None, min_length=3, max_length=50)
    email: EmailStr | None = None


class UserResponse(UserBase):
    id: int
    is_active: bool
    created_at: datetime

    model_config = {"from_attributes": True}


class UserListResponse(BaseModel):
    total: int

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Check the request body: both fields present and equal before submit (client-side `password === passwordConfirm`).
  2. If the 422 lists the error under `password_confirm`, the values differ — re-enter and resubmit.
  3. Confirm the schema field name matches what the client sends (`password_confirm`, not `passwordConfirm` or `confirm_password`).
  4. If you do not want a 422, drop the validator and rely on a server-side hash+compare; but the mismatch check is the recommended pattern.

Example fix

# before
@model_validator(mode="after")
def passwords_match(self) -> "UserCreate":
    if self.password != self.password_confirm:
        raise ValueError("Passwords do not match")
    return self

# after — clear, attributed error so FastAPI's 422 points at the right field
from pydantic import model_validator
@model_validator(mode="after")
def passwords_match(self) -> "UserCreate":
    if self.password != self.password_confirm:
        raise ValueError({"password_confirm": "Passwords do not match"})
    return self
Defensive patterns

Strategy: validation

Validate before calling

# client-side, before submit
if form.password !== form.password_confirm:
    setFieldError('password_confirm', 'Passwords do not match')
    return
await fetch('/users/', {method:'POST', body: JSON.stringify(form)})

Type guard

from pydantic import BaseModel
def has_matching_passwords(data: dict) -> bool:
    return isinstance(data, dict) and data.get('password') == data.get('password_confirm') and bool(data.get('password'))

Try / catch

from pydantic import ValidationError
try:
    user_in = UserCreate(**payload)
except ValidationError as e:
    # 422 body; surface password_confirm error to that input
    return json_response(e.errors(), status=422)

Prevention

When it happens

Trigger: POST `/users/` (or `/`) with a JSON body where `password` and `password_confirm` differ, or where `password_confirm` is missing (Pydantic raises a missing-field error first). Front-end sends a stale form after the user edited one password box but not the second.

Common situations: JS client only sends `password` and forgets `password_confirm` (422 on missing field, not the mismatch). Two password inputs get out of sync due to autofill or paste. Tests construct `UserCreate(...)` directly with mismatched values.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/a66ddefc7cf9d24b. Report an issue: GitHub.