Skip to content

ingest: stop routing plain .md/.txt through MIME extraction (fixes #1)#2

Open
owrede wants to merge 1 commit into
ContextFit:masterfrom
owrede:fix/mime-gate-non-ascii
Open

ingest: stop routing plain .md/.txt through MIME extraction (fixes #1)#2
owrede wants to merge 1 commit into
ContextFit:masterfrom
owrede:fix/mime-gate-non-ascii

Conversation

@owrede

@owrede owrede commented Jul 5, 2026

Copy link
Copy Markdown

Fixes #1.

Root cause

_preprocess_file routes every discovered file through _extract_email_text. Anything that isn't a markdown-exported email falls into the raw-MIME branch, where compat32 get_payload(decode=True) on a str payload re-encodes via raw-unicode-escape and then decodes with utf-8, errors="ignore". Net effect on plain text:

  • every char in U+0080–U+00FF is silently deleted: KölnKln, StraßeStrae
  • every char above U+00FF becomes a literal \uXXXX sequence in the indexed text

For any non-English corpus this silently corrupts the entire index — the source files are untouched, so it's easy to miss.

Fix

  1. Gate MIME extraction behind a new _looks_like_email(path, raw) helper: only .eml files, markdown-exported emails (# Email: / **From:**), or files opening with RFC-5322 headers go through _extract_email_text; everything else is ingested verbatim.
  2. Read files as explicit UTF-8 (read_text(encoding="utf-8", errors="ignore")) instead of the locale default.

Downstream behavior is unchanged: is_email still derives from email_meta keys, and the non-email branch returns {"source": str(path)} — truthy, same as _extract_email_text's fallback, so email_meta or auto_extractor.extract(...) resolves identically.

Tests

  • New tests/test_ingest_unicode.py: gating unit tests + an end-to-end regression test that runs _preprocess_file on a .md containing umlauts/CJK/emoji and asserts the decoded token stream still contains them.
  • All existing tests in tests/test_email_calendar_ingestion.py pass.

Suggested follow-up (not in this PR)

The gate removes the trigger, but the lossy round-trip inside _extract_email_text remains reachable for real 8bit-encoded .eml files. Root fix: parse real emails from bytes — email.message_from_bytes(raw.encode("utf-8"), policy=email.policy.default) — so get_payload(decode=True) returns properly CTE-decoded bytes. Happy to send that as a second PR if wanted.

🤖 Generated with Claude Code

…ntextFit#1)

_preprocess_file sent every discovered file through _extract_email_text.
For anything that is not a markdown-exported email, that takes the raw
MIME path: compat32 get_payload(decode=True) on a str payload round-trips
the text through raw-unicode-escape + utf-8/ignore, silently deleting all
chars in U+0080..U+00FF (umlauts, ß, accents) and turning chars above
U+00FF into literal \uXXXX sequences. A German-language vault loses every
umlaut in the index ('Köln' -> 'Kln').

Gate MIME extraction behind _looks_like_email(): only .eml files,
markdown-exported emails (# Email: / **From:**), or files opening with
RFC-5322 headers are parsed as email; everything else is ingested
verbatim. Also read files as explicit UTF-8 instead of the locale
default.

Regression test ingests a .md with umlauts/CJK/emoji end-to-end and
asserts the decoded token stream still contains them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ingest: MIME email extraction silently deletes all non-ASCII characters from plain .md/.txt files

1 participant