{"record":{"id":"948c452638a7c163","repo":"deepset-ai/haystack","slug":"found-unclosed-haystack-content-part-tag-at-posi","errorCode":null,"errorMessage":"Found unclosed <haystack_content_part> tag at position {tag_start}. Content: '{snippet}...'","messagePattern":"Found unclosed <haystack_content_part> tag at position (.+?)\\. Content: '(.+?)\\.\\.\\.'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/utils/jinja2_chat_extension.py","lineNumber":331,"sourceCode":"            if tag_start == -1:\n                # No more tags, add remaining text if any\n                remaining_text = content[cursor:].strip()\n                if remaining_text:\n                    parts.append(TextContent(text=remaining_text))\n                break\n\n            # Add text before tag if any\n            if tag_start > cursor:\n                plain_text = content[cursor:tag_start].strip()\n                if plain_text:\n                    parts.append(TextContent(text=plain_text))\n\n            content_start = tag_start + len(start_tag)\n            tag_end = content.find(end_tag, content_start)\n\n            if tag_end == -1:\n                snippet = _redact_nonce(content, start_tag, end_tag)[tag_start : tag_start + 50]\n                raise ValueError(\n                    f\"Found unclosed <haystack_content_part> tag at position {tag_start}. Content: '{snippet}...'\"\n                )\n\n            json_content = content[content_start:tag_end]\n            data = json.loads(json_content)\n            parts.append(_deserialize_content_part(data))\n\n            cursor = tag_end + len(end_tag)\n\n        return parts\n\n    @staticmethod\n    def _validate_build_chat_message(\n        parts: list[ChatMessageContentT], role: str, meta: dict, name: str | None = None\n    ) -> ChatMessage:\n        \"\"\"\n        Validate the parts of a chat message and build a ChatMessage object.\n","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/utils/jinja2_chat_extension.py#L313-L349","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\n{% if image %}<part-start {{ image }}\n{% endif %}<part-end>  // tags split across blocks\n// after\n{% if image %}<part-start {{ image }}><part-end>{% endif %}","handlingStrategy":"validation","validationCode":"def validate_part_tags_balanced(rendered: str, start_tag: str, end_tag: str) -> None:\n    if rendered.count(start_tag) != rendered.count(end_tag):\n        raise ValueError(\"Unbalanced <haystack_content_part> sentinel tags in rendered template\")","typeGuard":"def tags_balanced(rendered: str, start_tag: str, end_tag: str) -> bool:\n    return rendered.count(start_tag) == rendered.count(end_tag)","tryCatchPattern":"try:\n    messages = renderer.run(template=tpl, variables=vars)[\"messages\"]\nexcept ValueError as e:\n    if \"unclosed <haystack_content_part>\" in str(e):\n        log.error(\"Sentinel tag structure broken; check Jinja2 blocks around content parts\")\n    raise","preventionTips":["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."],"tags":["jinja2","chat-template","malformed-template"],"backgroundTag":"unclosed-template-tag","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}