Skip to content

fix: MarkdownHeaderSplitter drops content before an embedded header or code-fence comment - #11920

Merged
sjrl merged 1 commit into
deepset-ai:mainfrom
otiscuilei:fix/markdown-splitter-content-loss
Jul 9, 2026
Merged

fix: MarkdownHeaderSplitter drops content before an embedded header or code-fence comment#11920
sjrl merged 1 commit into
deepset-ai:mainfrom
otiscuilei:fix/markdown-splitter-content-loss

Conversation

@otiscuilei

Copy link
Copy Markdown
Contributor

What

MarkdownHeaderSplitter silently drops document content when keep_headers=False and a secondary split is configured.

In _apply_secondary_splitting, the header-stripping step used a non-anchored re.search(self._header_pattern, doc.content). Because the header pattern is (?m)^(#{1,6}) (.+)$ (MULTILINE), re.search matches the first # line anywhere in the chunk, not just a genuine leading header. Everything before that match (doc.content[header_match.end():]) is then discarded.

This triggers whenever a chunk contains a # line that is not its leading header, for example:

  • an embedded lower-level header that was merged into the chunk (e.g. a ## when header_split_levels=[1]), or
  • a # comment inside a fenced code block (e.g. a Python comment).

Anyone using MarkdownHeaderSplitter(keep_headers=False, secondary_split=...) on realistic Markdown (nested sections or code samples) loses the prose that precedes the first such # line.

Fix

Anchor the match to the start of the chunk with re.match instead of re.search, so only a header at the very beginning of the content is stripped. re.match anchors to position 0 regardless of the MULTILINE flag, leaving embedded headers and code-fence comments untouched.

-header_match = re.search(self._header_pattern, doc.content)
+header_match = re.match(self._header_pattern, doc.content)

Before / after

from haystack import Document
from haystack.components.preprocessors.markdown_header_splitter import MarkdownHeaderSplitter

splitter = MarkdownHeaderSplitter(
    keep_headers=False, header_split_levels=[1], secondary_split="word", split_length=100
)
content = "# Main\nsome intro text\n```python\n# a comment in code\n```\nmore text\n"
docs = splitter.run(documents=[Document(content=content)])["documents"]
combined = "".join(d.content or "" for d in docs)

# Before: "\n```\nmore text\n"   -> "some intro text" and the code fence are lost
# After:  "\nsome intro text\n```python\n# a comment in code\n```\nmore text\n"

Tests

Two regression tests in test/components/preprocessors/test_markdown_header_splitter.py, one per trigger case:

  • test_secondary_split_keeps_content_before_embedded_header
  • test_secondary_split_keeps_content_before_code_fence_comment

Both fail on the pre-fix source (content dropped) and pass with the fix; the full file (34 tests) passes. A release note is included under releasenotes/notes/.

@otiscuilei
otiscuilei requested a review from a team as a code owner July 8, 2026 19:16
@otiscuilei
otiscuilei requested review from sjrl and removed request for a team July 8, 2026 19:16
@vercel

vercel Bot commented Jul 8, 2026

Copy link
Copy Markdown

@Otis0408 is attempting to deploy a commit to the deepset Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant

CLAassistant commented Jul 8, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@sjrl sjrl self-assigned this Jul 9, 2026
@github-actions github-actions Bot added the type:documentation Improvements on the docs label Jul 9, 2026
Comment thread releasenotes/notes/fix-markdown-splitter-content-loss-3e4f5a6b7c8d9012.yaml Outdated
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  haystack/components/preprocessors
  markdown_header_splitter.py 228
Project Total  

This report was generated by python-coverage-comment-action

With keep_headers=False the secondary split re-stripped a leading header using a
non-anchored re.search, which matched the first '#' line anywhere in the body (an
embedded lower-level header, or a '#' comment inside a fenced code block) and
discarded everything before it. Anchor the match to the start of the content with
re.match so only a genuine leading header is stripped.
@otiscuilei
otiscuilei force-pushed the fix/markdown-splitter-content-loss branch from 399ad7a to 440f4fc Compare July 9, 2026 05:29

@sjrl sjrl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes!

@sjrl
sjrl merged commit 31464b3 into deepset-ai:main Jul 9, 2026
23 of 24 checks passed
@otiscuilei

Copy link
Copy Markdown
Contributor Author

Thanks for the quick review and merge!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic:tests type:documentation Improvements on the docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants