TL;DR: Pydantic v2 requires every model attribute to carry a type annotation; a bare assignment like `x = 5` is rejected because v2 cannot tell a field from a class constant. Annotate real fields (`x: int = 5`) and mark constants with ClassVar. The model then imports cleanly and behaves the same as under v1.

```text
pydantic.errors.PydanticUserError: A non-annotated attribute was detected
```

1. Read the traceback to find the model class and the attribute name it complains about.
   Expected: you can point at the exact class and the bare assignment.
2. If the attribute is a model field, add an annotation: change `x = 5` to `x: int = 5`.
   Expected: the module imports without raising PydanticUserError.
3. If the attribute is a class constant rather than a field, annotate it with ClassVar: `NAME: ClassVar[str] = "value"`, importing ClassVar from typing.
   Expected: the constant is excluded from the model's fields and the error is gone.
4. Re-run the code path that raised the error (app startup, test, or script).
   Expected: no PydanticUserError; the model validates as before.
5. For a large codebase, run the bump-pydantic codemod to convert models automatically, then review its diff.
   Expected: the codemod reports the files it changed; you confirm each change looks right.

## Use this when
- The traceback names PydanticUserError with the "non-annotated attribute" message.
- You just upgraded from pydantic v1 to v2 and models that used to import now fail.
- The model class contains bare assignments with no type annotation.

## Not for this skill when
- The PydanticUserError message is different (validator misuse, protected-namespace warnings); each has its own fix.
- Pydantic v1 is still installed; this error is v2-only.
- The failing model lives in a third-party library; pin that library or upgrade it rather than editing your own code.

## Variant phrasings
- "pydantic v2 non-annotated attribute"
- "A non-annotated attribute was detected in class"
- "pydantic 2 migration bare class attribute error"
- "PydanticUserError after upgrading pydantic"

## Why it happens
Pydantic v1 silently ignored non-annotated attributes on models. V2 builds an explicit schema for every model, so an unannotated assignment is ambiguous: is it a field with an inferred type, or a plain class constant? Rather than guess, v2 raises and asks you to declare intent with an annotation or ClassVar.

## Edge cases
- ClassVar must be imported from typing; forgetting the import gives a NameError instead.
- Private attributes (underscore-prefixed) still need annotations in v2; the underscore alone is not enough.
- The same error appears in pydantic dataclasses; the fix is identical.
- If you used bare attributes as a shortcut for "ignore this", ClassVar is the correct v2 spelling of that intent.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_pn2AA_YQJlpAHxJMdVLoJA
