Jump to solution
Verify

The Fix

pip install pydantic==2.10.6

Based on closed pydantic/pydantic issue #11923 · PR/commit linked

Jump to Verify Open PR/Commit
@@ -967,51 +967,6 @@ except ValidationError as e: ``` -As seen above, annotating a field with a `BaseModel` type can be used to modify or override the generated json schema. -However, if you want to take advantage of storing metadata via `Annotated`, but you don't want to override the generated JSON -schema, you can use the following approach with a no-op version of `__get_pydantic_core_schema__` implemented on the
repro.py
from typing import TypeVar, Generic, get_args, GenericAlias from pydantic import TypeAdapter, GetCoreSchemaHandler from pydantic.dataclasses import dataclass from pydantic_core.core_schema import tagged_union_schema from pydantic._internal._core_utils import pretty_print_core_schema T = TypeVar('T') U = TypeVar('U') class Parent(Generic[T]): subs: list[type] = [] @classmethod def __init_subclass__(cls): super(Parent, cls).__init_subclass__() Parent.subs.append(cls) @classmethod def __get_pydantic_core_schema__(cls: type, actual: type, handler: GetCoreSchemaHandler): if cls is Parent: args = get_args(actual) sub_types = tuple(GenericAlias(sub, args) for sub in cls.subs) pretty_print_core_schema(handler(sub_types[0])) return tagged_union_schema( {t.__name__: handler(t) for t in sub_types}, discriminator="type", ref="Parent" ) else: return handler(cls) @dataclass class C1(Parent[U]): c1: U @dataclass class C2(Parent[U]): c2: U a = TypeAdapter(Parent[int]).validate_python({"type": "C1", "c1": "42"}) # Assertion passes in 2.10 but fails in 2.11 as c1 is the string "42" instead of an int assert a.c1 == 42
verify
Re-run: uv run --with 'pydantic<2.11' --with rich demo.py Re-run: uv run --with 'pydantic>=2.11' --with rich demo.py
fix.md
Option A — Upgrade to fixed release\npip install pydantic==2.10.6\nWhen NOT to use: This fix should not be applied if the existing behavior is required for backward compatibility.\n\n

Why This Fix Works in Production

  • Trigger: AssertionError
  • Mechanism: Optimizes calls to `get_type_ref` to reduce unnecessary function calls during schema generation, addressing issues with schema generation in Pydantic V2.11.
  • Why the fix works: Optimizes calls to `get_type_ref` to reduce unnecessary function calls during schema generation, addressing issues with schema generation in Pydantic V2.11. (first fixed release: 2.10.6).
Production impact:
  • If left unfixed, this can cause silent data inconsistencies that propagate (bad cache entries, incorrect downstream decisions).

Why This Breaks in Prod

  • Triggered by an upgrade/regression window: 2.11 breaks; 2.10.6 is the first fixed release.
  • Shows up under Python 3.12 in real deployments (not just unit tests).
  • Surfaces as: $ uv run --with 'pydantic<2.11' --with rich demo.py

Proof / Evidence

  • GitHub issue: #11923
  • Fix PR: https://github.com/pydantic/pydantic/pull/10863
  • First fixed release: 2.10.6
  • Affected versions: 2.11
  • Reproduced locally: No (not executed)
  • Last verified: 2026-02-09
  • Confidence: 0.95
  • Did this fix it?: Yes (upstream fix exists)
  • Own content ratio: 0.38

Discussion

High-signal excerpts from the issue thread (symptoms, repros, edge-cases).

“Thank you for the quick response and fix.”
@tpoliaw · 2025-05-28 · source
“I'm not entirely sure why https://github.com/pydantic/pydantic/pull/10863 changed this, but anyway you can fix this by properly parameterizing your subclasses”
@Viicos · 2025-05-28 · source

Failure Signature (Search String)

  • AssertionError

Error Message

Stack trace
error.txt
Error Message ------------- $ uv run --with 'pydantic<2.11' --with rich demo.py {'type': 'definition-ref', 'schema_ref': '__main__.C1:976160128[int:131761114585728]'} $ uv run --with 'pydantic>=2.11' --with rich demo.py {'type': 'definition-ref', 'schema_ref': '__main__.C1:261265008'} Traceback (most recent call last): File "/dls/athena/scanspec/basic.py", line 43, in <module> assert a.c1 == 42 ^^^^^^^^^^ AssertionError

Minimal Reproduction

repro.py
from typing import TypeVar, Generic, get_args, GenericAlias from pydantic import TypeAdapter, GetCoreSchemaHandler from pydantic.dataclasses import dataclass from pydantic_core.core_schema import tagged_union_schema from pydantic._internal._core_utils import pretty_print_core_schema T = TypeVar('T') U = TypeVar('U') class Parent(Generic[T]): subs: list[type] = [] @classmethod def __init_subclass__(cls): super(Parent, cls).__init_subclass__() Parent.subs.append(cls) @classmethod def __get_pydantic_core_schema__(cls: type, actual: type, handler: GetCoreSchemaHandler): if cls is Parent: args = get_args(actual) sub_types = tuple(GenericAlias(sub, args) for sub in cls.subs) pretty_print_core_schema(handler(sub_types[0])) return tagged_union_schema( {t.__name__: handler(t) for t in sub_types}, discriminator="type", ref="Parent" ) else: return handler(cls) @dataclass class C1(Parent[U]): c1: U @dataclass class C2(Parent[U]): c2: U a = TypeAdapter(Parent[int]).validate_python({"type": "C1", "c1": "42"}) # Assertion passes in 2.10 but fails in 2.11 as c1 is the string "42" instead of an int assert a.c1 == 42

Environment

  • Python: 3.12
  • Pydantic: 2

What Broke

Subtypes are not deserialized correctly, leading to assertion failures in production.

Fix Options (Details)

Option A — Upgrade to fixed release Safe default (recommended)

pip install pydantic==2.10.6

When NOT to use: This fix should not be applied if the existing behavior is required for backward compatibility.

Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.

Option D — Guard side-effects with OnceOnly Guardrail for side-effects

Mitigate duplicate external side-effects under retries/timeouts/agent loops by gating the operation before calling external systems.

  • Place OnceOnly between your code/agent and real side-effects (Stripe, emails, CRM, APIs).
  • Use a stable key per side-effect (e.g., customer_id + action + idempotency_key).
  • Fail-safe: configure fail-open vs fail-closed based on blast radius and spend risk.
  • This does NOT fix data corruption; it only prevents duplicate side-effects.
Show example snippet (optional)
onceonly.py
from onceonly import OnceOnly import os once = OnceOnly(api_key=os.environ["ONCEONLY_API_KEY"], fail_open=True) # Stable idempotency key per real side-effect. # Use a request id / job id / webhook delivery id / Stripe event id, etc. event_id = "evt_..." # replace key = f"stripe:webhook:{event_id}" res = once.check_lock(key=key, ttl=3600) if res.duplicate: return {"status": "already_processed"} # Safe to execute the side-effect exactly once. handle_event(event_id)

See OnceOnly SDK

When NOT to use: Do not use this to hide logic bugs or data corruption. Use it to block duplicate external side-effects and enforce tool permissions/spend caps.

Fix reference: https://github.com/pydantic/pydantic/pull/10863

First fixed release: 2.10.6

Last verified: 2026-02-09. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • This fix should not be applied if the existing behavior is required for backward compatibility.
  • Do not use this to hide logic bugs or data corruption. Use it to block duplicate external side-effects and enforce tool permissions/spend caps.

Verify Fix

verify
Re-run: uv run --with 'pydantic<2.11' --with rich demo.py Re-run: uv run --with 'pydantic>=2.11' --with rich demo.py

Did This Fix Work in Your Case?

Quick signal helps us prioritize which fixes to verify and improve.

Prevention

  • Add a CI check that diffs key outputs after upgrades (OpenAPI schema snapshots, JSON payload shapes, CLI output).
  • Upgrade behind a canary and run integration tests against the canary before 100% rollout.

Version Compatibility Table

VersionStatus
2.11 Broken
2.10.6 Fixed

Related Issues

No related fixes found.

Sources

We don’t republish the full GitHub discussion text. Use the links above for context.