[BUG] Custom prompt templates always inject the ReAct `task` slice — wrong instructions for no-tool and native-tool agents
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 58.8k
- Forks
- 8.5k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 109
Description
Description
Prompts._build_prompt has two code paths: the default one joins the i18n slices selected in task_execution (role_playing, plus tools/no_tools, plus exactly one task slice: task, native_task, or task_no_tools). The custom-template path (taken when both system_template and prompt_template are set) ignores which task slice was selected:
{{ .System }}is filled with every component except the literal"task", sonative_taskandtask_no_toolsare not excluded — the selected task slice leaks into the system section.{{ .Prompt }}is always filled with the hardcoded"task"slice regardless of the selection.
The "task" slice contains ReAct-only instructions: "use the tools available and give your best Final Answer, your job depends on it!" and a Thought: seed. The result:
- An agent with no tools (
task_no_toolsselected) is told to "use the tools available" and gets aThought:ReAct scaffold — the exact thought-leakage problem theuse_system_promptpath was fixed for. - An agent using native function calling (
native_taskselected) gets text ReAct instructions that conflict with the native calling flow. - In both cases
Current Task: {input}is emitted twice — once via the unfiltered task slice in{{ .System }}and again via the hardcodedtaskslice in{{ .Prompt }}.
Only the tool-enabled ReAct configuration (task selected) produces correct output today.
Steps to Reproduce
from unittest.mock import MagicMock
from crewai.utilities.prompts import Prompts
agent = MagicMock()
agent.role, agent.goal, agent.backstory = "Writer", "Write", "Pro"
# No tools + custom templates
prompt = Prompts(
has_tools=False, use_native_tool_calling=False, use_system_prompt=False,
agent=agent,
system_template="system:\n{{ .System }}",
prompt_template="user:\n{{ .Prompt }}",
).task_execution()["prompt"]
print(prompt)
# Actual (buggy): system section contains "Current Task: {input}\nProvide your
# complete response:" AND the user section contains the "task" slice:
# "use the tools available and give your best Final Answer ... Thought:"
# Expected: user section contains "task_no_tools" ("Provide your complete
# response:"), no tool instructions, "Current Task:" appears exactly once.
# Same failure with native tool calling:
prompt = Prompts(
has_tools=True, use_native_tool_calling=True, use_system_prompt=False,
agent=agent,
system_template="system:\n{{ .System }}",
prompt_template="user:\n{{ .Prompt }}",
).task_execution()["prompt"]
# Actual (buggy): user section contains the ReAct "task" slice
# ("Thought:" seed, "use the tools available"), and "Current Task: {input}"
# appears twice.
The regression tests in lib/crewai/tests/utilities/test_prompts_no_thought_leakage.py (TestCustomTemplatesPromptGeneration) reproduce both cases — they fail on the unpatched code and pass with the fix; a third test pins the unchanged ReAct-with-tools behavior.
Expected behavior
With custom system_template / prompt_template, {{ .Prompt }} is filled with the task slice that task_execution selected (task_no_tools for an agent without tools, native_task for native tool calling, task otherwise), and that slice does not also appear in {{ .System }}.
Screenshots/Code snippets
lib/crewai/src/crewai/utilities/prompts.py, Prompts._build_prompt, custom-template branch:
- Line 239:
if component != "task"— only excludes thetaskslice, notnative_task/task_no_tools. - Line 245:
"{{ .Prompt }}", "".join(I18N_DEFAULT.slice("task"))— hardcodes thetaskslice.
components is built in task_execution (lines 99–118) which appends one of task / native_task / task_no_tools last based on has_tools and use_native_tool_calling. Reached from Agent._build_execution_prompt (lib/crewai/src/crewai/agent/core.py:1133) whenever an agent sets system_template + prompt_template.
Operating System
Ubuntu 22.04
Python Version
3.12
crewAI Version
1.15.22 (main @ 5c33fe4)
crewAI Tools Version
1.15.22
Virtual Environment
Venv
Evidence
Any agent configured with custom system_template/prompt_template that either has no tools or uses native tool calling receives a prompt that (a) instructs it to use tools it does not have / should not call textually, (b) seeds a Thought: prefix that leaks into the final output, and (c) duplicates the Current Task: block. This silently degrades output quality on a documented configuration path (Agent(system_template=..., prompt_template=...)).
Possible Solution
In the custom-template branch, treat all three task-family slices (task, native_task, task_no_tools) as the task component: exclude all of them from the {{ .System }} parts, and fill {{ .Prompt }} with whichever one is present in components (falling back to "task" when none is, preserving prior behavior):
task_components = {"task", "native_task", "task_no_tools"}
template_parts = [
I18N_DEFAULT.slice(c) for c in components if c not in task_components
]
system = system_template.replace("{{ .System }}", "".join(template_parts))
task_component = next((c for c in components if c in task_components), "task")
prompt = prompt_template.replace("{{ .Prompt }}", I18N_DEFAULT.slice(task_component))
Additional context
Found and written with an AI coding agent (Devin, reviewed by Claude Code); please apply the llm-generated label. A fix with regression tests is ready and will be linked here.
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 in lib/crewai/src/crewai/utilities/prompts.py at Prompts._build_prompt and trace components from task_execution; Agent._build_execution_prompt in lib/crewai/src/crewai/agent/core.py:1133 shows the entry point. Run lib/crewai/tests/utilities/test_prompts_no_thought_leakage.py and confirm custom templates select the matching task slice, exclude it from the system section, and avoid duplicate Current Task text while preserving ReAct behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- ai
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 90/100