Skip to content

fix(serialization): apply exclude recursively for keys nested inside Pydantic models#6595

Open
lntutor wants to merge 1 commit into
crewAIInc:mainfrom
lntutor:fix/serialization-exclude-nested-models
Open

fix(serialization): apply exclude recursively for keys nested inside Pydantic models#6595
lntutor wants to merge 1 commit into
crewAIInc:mainfrom
lntutor:fix/serialization-exclude-nested-models

Conversation

@lntutor

@lntutor lntutor commented Jul 19, 2026

Copy link
Copy Markdown

What's broken

to_serializable(obj, exclude={...}) applies exclude recursively for dicts (filtered at every level), lists, and dataclasses (both forward exclude into recursive calls). The BaseModel branch is the exception: it passes exclude to model_dump() — which only drops top-level fields (a set exclude is not recursive in Pydantic v2) — and then recurses into the resulting dict without forwarding exclude:

class Address(BaseModel):
    street: str
    secret: str
class Person(BaseModel):
    name: str
    address: Address

to_serializable(Person(name="John", address=Address(street="123 Main St", secret="TOP-SECRET")), exclude={"secret"})
# {'name': 'John', 'address': {'street': '123 Main St', 'secret': 'TOP-SECRET'}}  <- 'secret' leaked

Since to_serializable serializes agent/task/event state (e.g. to_string), this leaks fields a caller explicitly asked to drop — potentially sensitive data.

Fix

Forward exclude into the BaseModel branch's recursive calls (both the model_dump path and the __dict__ fallback), relying on the existing recursive dict-branch filtering. Top-level exclusion is unchanged.

Tests

Extends lib/crewai/tests/utilities/test_serialization.py: a nested-model exclude case, and a parity check that the model path matches the plain-dict path. Full file passes (27). Distinct from #2479 (which added the exclude option).

…Pydantic models

to_serializable() forwards exclude recursively for dicts, lists, and
dataclasses, but the BaseModel branch only passed exclude to
model_dump() -- which drops top-level fields only (a set exclude is not
recursive in Pydantic v2) -- and then dropped exclude when recursing
into the resulting dict. So an excluded key nested inside a model (e.g.
a 'secret' field on a nested Address) leaked into the output, defeating
a caller's explicit request to drop a potentially sensitive field.

Forward exclude into the model branch's recursive calls so nested
exclusion is consistent with every other container type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N6RtoHuxrDqTUo9Mw9h4Cv
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 49c0eca7-e486-4c3b-9630-25745dd4d655

📥 Commits

Reviewing files that changed from the base of the PR and between 69c0308 and 420994a.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/utilities/serialization.py
  • lib/crewai/tests/utilities/test_serialization.py

📝 Walkthrough

Walkthrough

to_serializable now propagates exclude through nested Pydantic serialization, including its fallback path. Tests verify recursive field exclusion and matching output for nested Pydantic models and equivalent dictionaries.

Changes

Serialization exclusions

Layer / File(s) Summary
Recursive exclusion handling
lib/crewai/src/crewai/utilities/serialization.py, lib/crewai/tests/utilities/test_serialization.py
Nested model serialization and fallback field serialization now forward exclude; tests cover recursive exclusion and parity with plain dictionary input.

Suggested reviewers: vinibrsl

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: recursive exclude handling for nested Pydantic model fields.
Description check ✅ Passed The description matches the changeset and explains the bug, fix, and tests for recursive exclude handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant