deepset-ai/haystack · error · ValueError
Found unclosed <haystack_content_part> tag at position {tag_
Error message
Found unclosed <haystack_content_part> tag at position {tag_start}. Content: '{snippet}...' What it means
The rendered template text contains an internal `<haystack_content_part>` sentinel opening tag whose matching closing tag was not found, so the content-part parser cannot extract the serialized part (e.g. an ImageContent or ToolCall). This indicates the sentinel structure was corrupted, typically by content truncation or by template text interfering with the sentinel format.
Source
Thrown at haystack/utils/jinja2_chat_extension.py:331
if tag_start == -1:
# No more tags, add remaining text if any
remaining_text = content[cursor:].strip()
if remaining_text:
parts.append(TextContent(text=remaining_text))
break
# Add text before tag if any
if tag_start > cursor:
plain_text = content[cursor:tag_start].strip()
if plain_text:
parts.append(TextContent(text=plain_text))
content_start = tag_start + len(start_tag)
tag_end = content.find(end_tag, content_start)
if tag_end == -1:
snippet = _redact_nonce(content, start_tag, end_tag)[tag_start : tag_start + 50]
raise ValueError(
f"Found unclosed <haystack_content_part> tag at position {tag_start}. Content: '{snippet}...'"
)
json_content = content[content_start:tag_end]
data = json.loads(json_content)
parts.append(_deserialize_content_part(data))
cursor = tag_end + len(end_tag)
return parts
@staticmethod
def _validate_build_chat_message(
parts: list[ChatMessageContentT], role: str, meta: dict, name: str | None = None
) -> ChatMessage:
"""
Validate the parts of a chat message and build a ChatMessage object.
View on GitHub (pinned to e318778c9b)
Solutions
- Ensure the opening content-part tag and its closing tag are inside the same Jinja2 conditional/block so they are emitted together.
- Do not place {% if %}/{% endif %} boundaries between a content part's start and end tags.
- Check for template truncation (e.g. missing end of string) and re-validate the full template syntax.
- If application data may contain tag-like text, escape it or keep it out of content-part regions.
Example fix
// before
{% if image %}<part-start {{ image }}
{% endif %}<part-end> // tags split across blocks
// after
{% if image %}<part-start {{ image }}><part-end>{% endif %} Defensive patterns
Strategy: validation
Validate before calling
def validate_part_tags_balanced(rendered: str, start_tag: str, end_tag: str) -> None:
if rendered.count(start_tag) != rendered.count(end_tag):
raise ValueError("Unbalanced <haystack_content_part> sentinel tags in rendered template") Type guard
def tags_balanced(rendered: str, start_tag: str, end_tag: str) -> bool:
return rendered.count(start_tag) == rendered.count(end_tag) Try / catch
try:
messages = renderer.run(template=tpl, variables=vars)["messages"]
except ValueError as e:
if "unclosed <haystack_content_part>" in str(e):
log.error("Sentinel tag structure broken; check Jinja2 blocks around content parts")
raise Prevention
- Keep each content part's opening and closing sentinel tags within one Jinja2 block.
- Never split content-part tags across {% if %}/{% endif %} boundaries.
- Escape or sanitize external data that may contain tag-like markup.
- Test multimodal templates end-to-end with real image/file inputs.
When it happens
Trigger: Template content gets truncated mid part-tag; user-provided text containing strings resembling the sentinel tags; a Jinja2 block breaks before the closing tag is emitted (e.g. an exception mid-render swallowed, or unbalanced Jinja2 tags).
Common situations: Multimodal templates with images/files where the closing tag ends up outside a Jinja2 conditional; documents containing odd markup that disrupts parsing; copying an incomplete template example.
Related errors
- The '{% insert %}' expression must evaluate to a ChatMessage
- Message content in template is empty or contains only whites
- Invalid template for condition: {condition_value}
- Invalid template for output: {output!r} (type: {type(output)
- Invalid template for output: {output}
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/948c452638a7c163.
Report an issue: GitHub.