[BUG] Crew.copy() mis-wires task context on duplicate task text and raises KeyError for external context tasks
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 58.8k
- Forks
- 8.5k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 109
Description
Description
Crew.copy() re-wires task context links by looking tasks up in a task_mapping dictionary keyed by Task.key, which is md5(description | expected_output). Identifying tasks by their text rather than by object identity causes two separate problems:
- Silent mis-wiring. Two tasks with the same
descriptionandexpected_outputproduce the same key, so the second overwrites the first in the mapping. A cloned task'scontextis then wired to the wrong clone, with no error. - Crash. If a task's
contextreferences aTaskthat isn't increw.tasks, the lookup raisesKeyError. The crew validator explicitly allows this —validate_context_no_future_tasksskips context tasks that aren't crew members (crew.py:875) — so the crew builds andkickoff()runs, but copying it fails.
copy() isn't called directly by users; it's called by kickoff_for_each(), train() and test(), so all three break.
Steps to Reproduce
- Build a crew with two tasks having identical
descriptionandexpected_output, plus a third task whosecontextpoints at the first. Copy the crew and inspect the cloned context. - Build a crew with one task whose
contextreferences a task that is not increw.tasks, then callkickoff_for_each().
Expected behavior
Copying should preserve each task's context link to the correct corresponding clone regardless of duplicate task text, and a context task that isn't part of the crew should not crash the copy.
Screenshots/Code snippets
Mis-wiring:
t1 = Task(description="same", expected_output="same", agent=a)
t2 = Task(description="same", expected_output="same", agent=a)
t3 = Task(description="third", expected_output="third", agent=a, context=[t1])
c = Crew(agents=[a], tasks=[t1, t2, t3]).copy()
c.tasks[2].context[0] is c.tasks[0] # expected True
t1.key == 4612eff9ffb119e2b48d29b0cb544bb0
t2.key == 4612eff9ffb119e2b48d29b0cb544bb0
same key? True
t3.context points to -> clone of t2 (WRONG)
Crash:
outside = Task(description="outside", expected_output="o", agent=a)
inside = Task(description="inside {topic}", expected_output="i", agent=a, context=[outside])
crew = Crew(agents=[a], tasks=[inside]) # builds fine
crew.kickoff_for_each(inputs=[{"topic": "x"}])
crew built OK - validator allows an external context task
kickoff_for_each -> KeyError: '4c3d3b983623ce9b2b44460e9a58d6a4'
train() and test() fail identically. The error message is only an md5 hash, with no task name or hint about the cause.
Operating System
Other (specify in additional context)
Python Version
3.12
crewAI Version
1.15.18
crewAI Tools Version
1.15.18
Virtual Environment
Venv
Evidence
lib/crewai/src/crewai/task.py:601—Task.keyismd5("|".join([description, expected_output])), so it is not unique per task instance.lib/crewai/src/crewai/crew.py:2145-2162—task_mapping[task.key] = cloned_task, thentask_mapping[context_task.key]when re-wiring context.lib/crewai/src/crewai/task.py:1179—Task.copy()does the same unguardedtask_mapping[context_task.key]lookup; this is the one that raises first.lib/crewai/src/crewai/crew.py:875—validate_context_no_future_tasksskips context tasks not in the crew, so external context tasks are treated as valid.- Callers of
copy():crew.py:958(train),crew.py:1116(kickoff_for_each),crew.py:2254(test).
git log -S "task_mapping" shows this code has been unchanged since d1343b96e Release/v1.0.0 (#3618).
Reproduced on two machines with no LLM calls and no network.
Possible Solution
Key the mapping by id(task) instead of task.key, and fall back to the original object when a context task isn't in the mapping:
crew.py:task_mapping: dict[int, Any];task_mapping[id(task)] = cloned_task;task_mapping.get(id(context_task), context_task)task.py: signaturetask_mapping: dict[int, Task];task_mapping.get(id(context_task), context_task)
id() is unique per object, which removes the collision. The .get(..., context_task) fallback keeps a context task that isn't a crew member as-is, since there is no clone to point at.
Related observation, not addressed here: Task.copy() resolves the cloned agent with get_agent_by_role (task.py:1185), matching on the role string. Two agents sharing a role would have the same ambiguity. I haven't tested that and mention it only in case it's worth a separate look.
Additional context
My OS is macOS Tahoe 26.5.2 and I'm on Python 3.13.13, neither of which is in the dropdowns. Running from a source checkout of main at commit 92eb5f9.
This issue was written with AI assistance and should carry the llm-generated label per CONTRIBUTING.md. I can't apply labels myself — could a maintainer add it?
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 with the Crew.copy() and Task.copy() implementations at crew.py:2145-2162 and task.py:1179, then trace their callers in train(), kickoff_for_each(), and test(). Verify that duplicate task text preserves each context link and that external context tasks remain usable without raising; exercise the affected copy and execution paths with the reproduction cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- ai
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100