Auto-checkpoint raises PydanticSerializationError when a Task has a Python-callable guardrail
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 58.8k
- Forks
- 8.5k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 109
Description
Summary
With auto-checkpointing enabled, CheckpointListener._do_checkpoint calls state.model_dump(mode="json"), which fails on any Task whose guardrail is a Python callable (a function/lambda, as opposed to a string guardrail). Pydantic can't JSON-serialize the callable, so:
- A
UserWarning: Callable 'guardrail' cannot be JSON-serialized and will be dropped during checkpointing; restored checkpoints will not run this guardrail.fires, then model_dump(mode="json")still raisesPydanticSerializationError: Unable to serialize unknown type: <class 'function'>(also seen as<class 'pydantic._internal._model_construction.ModelMetaclass'>), which is re-raised out of_do_checkpointand logged by the event handler as a fullWARNING:crewai.state.checkpoint_listener:Auto-checkpoint failed for event task_completedtraceback.
Net effect: every checkpointed event on a guardrail-bearing task emits a traceback, and no usable checkpoint is written for that task.
Version
crewai 1.15.10 (also reproduces on 1.15.21). Python 3.12.
Actual behavior (real traceback)
UserWarning: Callable 'guardrail' cannot be JSON-serialized and will be dropped
during checkpointing; restored checkpoints will not run this guardrail.
WARNING:crewai.state.checkpoint_listener:Auto-checkpoint failed for event task_completed
Traceback (most recent call last):
File ".../crewai/state/checkpoint_listener.py", line 242, in _on_any_event
_do_checkpoint(state, cfg, event)
File ".../crewai/state/checkpoint_listener.py", line 144, in _do_checkpoint
payload = state.model_dump(mode="json")
pydantic_core._pydantic_core.PydanticSerializationError: Unable to serialize unknown type: <class 'function'>
Steps to reproduce
Configure a Crew with auto-checkpointing where on_events includes task_completed, and give one Task a callable guardrail (e.g. guardrail=my_fn where my_fn(output) -> (bool, Any)). Run kickoff(). Each task_completed triggers the failure above. String guardrails are unaffected.
Expected behavior
The checkpoint serializer should degrade gracefully on non-serializable fields (callable guardrails, and other non-JSON state) rather than raising: drop them from the snapshot with a single, quiet notice (not a per-event traceback), and still write the rest of the checkpoint. Callable guardrails are a first-class, documented feature, so checkpointing a task that uses one shouldn't fail or spam tracebacks.
Root cause
checkpoint_listener.py::_do_checkpoint -> state.model_dump(mode="json") has no default=/exclusion handling for callable/non-serializable fields; the except Exception at ~line 156 emits CheckpointFailedEvent and then raises, so the caller logs the whole traceback.
Workaround (downstream)
Restricting on_events to failure-only events (task_failed, crew_kickoff_failed) avoids the per-successful-task noise, but the underlying serialization gap remains for any checkpointed event on a guardrail-bearing task.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in checkpoint_listener.py, especially CheckpointListener._do_checkpoint and the _on_any_event path described in the traceback. Run the reported auto-checkpoint reproduction with a callable Task guardrail and verify that the remaining state is written without a per-event traceback, while the callable field is omitted with only one quiet notice.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100