The Fix
Fixes the issue where `RootModel.model_copy(deep=False)` did not create a shallow copy of the root value, causing unexpected mutations.
Based on closed pydantic/pydantic issue #12543 · PR/commit linked
@@ -99,7 +99,9 @@ def __copy__(self) -> Self:
cls = type(self)
m = cls.__new__(cls)
- _object_setattr(m, '__dict__', copy(self.__dict__))
+ new_dict = copy(self.__dict__)
+ new_dict['root'] = copy(self.__dict__['root'])
from pydantic import RootModel
def main():
list_1 = RootModel[list[int]]([1, 2, 3])
list_2 = list_1.model_copy(deep=False)
assert list_1.root is list_2.root
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Apply the official fix\nFixes the issue where `RootModel.model_copy(deep=False)` did not create a shallow copy of the root value, causing unexpected mutations.\nWhen NOT to use: Do not use this fix if a deep copy of the root value is required.\n\n
Why This Fix Works in Production
- Trigger: assert list_1.root is not list_2.root
- Mechanism: The `model_copy()` method did not create a shallow copy of the root value, leading to shared references
Why This Breaks in Prod
- The `model_copy()` method did not create a shallow copy of the root value, leading to shared references
- Production symptom (often without a traceback): assert list_1.root is not list_2.root
Proof / Evidence
- GitHub issue: #12543
- Fix PR: https://github.com/pydantic/pydantic/pull/12679
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.70
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.71
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“Check out the source code of the .model_copy(), if u want a shallow copy, u should use: @davidhewitt could close this”
“Thank you @Viicos for explaining my issue better. This is precisely what I mean.”
“While I don't think this is a bug, the current behavior is debatable”
“Thanks, as @yinxiangshi says, .model_copy() supports both shallow and deep copies. By default it is a shallow copy. If you pass deep=True, you get a…”
Failure Signature (Search String)
- assert list_1.root is not list_2.root
- assert list_1.root is list_2.root
Copy-friendly signature
Failure Signature
-----------------
assert list_1.root is not list_2.root
assert list_1.root is list_2.root
Error Message
Signature-only (no traceback captured)
Error Message
-------------
assert list_1.root is not list_2.root
assert list_1.root is list_2.root
Minimal Reproduction
from pydantic import RootModel
def main():
list_1 = RootModel[list[int]]([1, 2, 3])
list_2 = list_1.model_copy(deep=False)
assert list_1.root is list_2.root
Environment
- Pydantic: 2
What Broke
Using `model_copy()` resulted in unexpected mutations due to shared root references.
Why It Broke
The `model_copy()` method did not create a shallow copy of the root value, leading to shared references
Fix Options (Details)
Option A — Apply the official fix
Fixes the issue where `RootModel.model_copy(deep=False)` did not create a shallow copy of the root value, causing unexpected mutations.
Fix reference: https://github.com/pydantic/pydantic/pull/12679
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- Do not use this fix if a deep copy of the root value is required.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
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.
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.