Skip to content

fix(core): preserve malformed frontmatter during sync - #1188

Merged
phernandez merged 1 commit into
mainfrom
codex/frontmatter-fail-closed
Aug 4, 2026
Merged

fix(core): preserve malformed frontmatter during sync#1188
phernandez merged 1 commit into
mainfrom
codex/frontmatter-fail-closed

Conversation

@phernandez

Copy link
Copy Markdown
Member

Why

Fixes #1171. Supersedes #1173.

When sync adds or updates a permalink, FileService.update_frontmatter_with_result() currently
treats an unparseable fenced YAML block as ordinary Markdown and prepends a new frontmatter block.
That creates the double-frontmatter state reported in #1171.

#1173 found this writer path, but its proposed repair strips the malformed block and writes only
the requested fields. A permalink-only sync can therefore delete the note's title, description,
tags, timestamps, and other user metadata. This replacement keeps the root-cause fix while
preserving Markdown as the authoritative user-owned representation.

What Changed

  • Reject a frontmatter update when the existing fenced YAML cannot be parsed safely.
  • Leave the source file byte-for-byte unchanged instead of stacking or replacing metadata.
  • Report malformed files through the batch-indexing error result without creating derived entity
    or search state from a partial rewrite.
  • Add service-level and batch-indexing regressions for the destructive case identified during the
    review of fix(core): stop frontmatter merge from stacking a second block on parse failure #1173.

Implementation Details

FileService now parses an existing frontmatter block before removing or rewriting any bytes. A
ParseError becomes a specific FileOperationError, and the existing outer error boundary
preserves that error instead of wrapping it a second time.

Valid frontmatter merges and frontmatter creation are unchanged. The deliberate tradeoff is that
a malformed note must be repaired by its owner before Basic Memory can normalize its permalink;
the system will not guess how to reconstruct invalid YAML or discard fields it cannot understand.

The batch-indexing regression exercises the real storage writer and database path. It proves that
the file is reported as an error, remains unchanged, and produces no entity projection.

Testing

Automated

  • BASIC_MEMORY_ENV=test LOGFIRE_IGNORE_NO_CONFIG=1 uv run pytest -q tests/services/test_file_service.py tests/indexing/test_batch_indexer.py --no-cov: 38 passed.
  • just fast-check: Ruff fix/check, formatting, and ty check src tests test-int passed.
  • git diff --check origin/main...HEAD: passed.
  • BASIC_MEMORY_ENV=test LOGFIRE_IGNORE_NO_CONFIG=1 just fast-test: testmon selected 4,172 tests because its cache was cold/mismatched; the run was stopped manually after 1,488 passed, 25 skipped, and no failures. This interrupted run is not counted as a passing gate.

Manual

Risks / Follow-ups

Signed-off-by: phernandez <paul@basicmachines.co>
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.

[BUG] Sync can prepend a SECOND frontmatter block, shadowing name/description from any parser that reads the first

1 participant