Skip to content

Commit 2ec60f6

Browse files
phernandezclaude
andcommitted
fix: Handle YAML parsing errors gracefully in update_frontmatter (#378)
The update_frontmatter() function was logging ERROR (level 17) for malformed YAML frontmatter instead of handling it gracefully. This caused 1,112+ errors per 3 hours in production from files with titles like "KB: Something" where the colon breaks YAML parsing. This fix applies the same error handling pattern from PR #368's entity_parser.py: - Catch ParseError and yaml.YAMLError when parsing frontmatter - Log as WARNING (level 13) instead of ERROR - Treat file as having no frontmatter and proceed with update - Only log ERROR for actual file operation failures Files with malformed frontmatter now get updated successfully with valid frontmatter instead of spamming error logs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
1 parent e6c8e36 commit 2ec60f6

2 files changed

Lines changed: 50 additions & 8 deletions

File tree

src/basic_memory/file_utils.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,20 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
210210
# Read current content
211211
content = path_obj.read_text(encoding="utf-8")
212212

213-
# Parse current frontmatter
213+
# Parse current frontmatter with proper error handling for malformed YAML
214214
current_fm = {}
215215
if has_frontmatter(content):
216-
current_fm = parse_frontmatter(content)
217-
content = remove_frontmatter(content)
216+
try:
217+
current_fm = parse_frontmatter(content)
218+
content = remove_frontmatter(content)
219+
except (ParseError, yaml.YAMLError) as e:
220+
# Log warning and treat as plain markdown without frontmatter
221+
logger.warning(
222+
f"Failed to parse YAML frontmatter in {path_obj}: {e}. "
223+
"Treating file as plain markdown without frontmatter."
224+
)
225+
# Keep full content, treat as having no frontmatter
226+
current_fm = {}
218227

219228
# Update frontmatter
220229
new_fm = {**current_fm, **updates}
@@ -229,11 +238,13 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
229238
return await compute_checksum(final_content)
230239

231240
except Exception as e: # pragma: no cover
232-
logger.error(
233-
"Failed to update frontmatter",
234-
path=str(path) if isinstance(path, (str, Path)) else "<unknown>",
235-
error=str(e),
236-
)
241+
# Only log real errors (not YAML parsing, which is handled above)
242+
if not isinstance(e, (ParseError, yaml.YAMLError)):
243+
logger.error(
244+
"Failed to update frontmatter",
245+
path=str(path) if isinstance(path, (str, Path)) else "<unknown>",
246+
error=str(e),
247+
)
237248
raise FileError(f"Failed to update frontmatter: {e}")
238249

239250

tests/utils/test_file_utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,37 @@ async def test_update_frontmatter_errors(tmp_path: Path):
259259
await update_frontmatter(nonexistent, {"title": "Test"})
260260

261261

262+
@pytest.mark.asyncio
263+
async def test_update_frontmatter_handles_malformed_yaml(tmp_path: Path):
264+
"""Test that update_frontmatter handles malformed YAML gracefully (issue #378)."""
265+
test_file = tmp_path / "test.md"
266+
267+
# Create file with malformed YAML frontmatter (colon in title breaks YAML)
268+
content = """---
269+
title: KB: Something
270+
---
271+
272+
# Test Content
273+
274+
Some content here"""
275+
test_file.write_text(content)
276+
277+
# Should handle gracefully and treat as having no frontmatter
278+
updates = {"title": "Fixed Title", "type": "note"}
279+
await update_frontmatter(test_file, updates)
280+
281+
# Verify file was updated successfully
282+
updated = test_file.read_text(encoding="utf-8")
283+
assert "title: Fixed Title" in updated
284+
assert "type: note" in updated
285+
assert "Test Content" in updated
286+
assert "Some content here" in updated
287+
288+
# Verify new frontmatter is valid
289+
fm = parse_frontmatter(updated)
290+
assert fm == updates
291+
292+
262293
@pytest.mark.asyncio
263294
def test_sanitize_for_filename_removes_invalid_characters():
264295
# Test all invalid characters listed in the regex

0 commit comments

Comments
 (0)