Compare commits

...

1 Commits

Author SHA1 Message Date
claude[bot] 42ce750b79 fix: handle malformed YAML in update_frontmatter gracefully
Fixes #378

- Changed error handling in update_frontmatter() to match PR #368 pattern
- YAML parsing errors now logged as WARNING instead of ERROR
- Files with malformed frontmatter treated as plain markdown
- Added test for malformed YAML case (KB: title format)

This resolves 1,112 errors/3hrs in production logs from files with
colons in titles that confuse the YAML parser.

Co-authored-by: Paul Hernandez <phernandez@users.noreply.github.com>
2025-10-17 00:08:04 +00:00
2 changed files with 55 additions and 9 deletions
+19 -9
View File
@@ -201,7 +201,6 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
Raises:
FileError: If file operations fail
ParseError: If frontmatter parsing fails
"""
try:
# Convert string to Path if needed
@@ -210,11 +209,20 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
# Read current content
content = path_obj.read_text(encoding="utf-8")
# Parse current frontmatter
# Parse current frontmatter with error handling for malformed YAML (issue #378)
current_fm = {}
if has_frontmatter(content):
current_fm = parse_frontmatter(content)
content = remove_frontmatter(content)
try:
current_fm = parse_frontmatter(content)
content = remove_frontmatter(content)
except (ParseError, yaml.YAMLError) as e:
# Log warning and treat as plain markdown (same pattern as PR #368)
logger.warning(
f"Failed to parse YAML frontmatter in {path_obj}: {e}. "
"Treating file as plain markdown without frontmatter."
)
# Keep full content, treat as having no frontmatter
current_fm = {}
# Update frontmatter
new_fm = {**current_fm, **updates}
@@ -229,11 +237,13 @@ async def update_frontmatter(path: FilePath, updates: Dict[str, Any]) -> str:
return await compute_checksum(final_content)
except Exception as e: # pragma: no cover
logger.error(
"Failed to update frontmatter",
path=str(path) if isinstance(path, (str, Path)) else "<unknown>",
error=str(e),
)
# Only log real errors as ERROR (not YAML parsing errors which are already handled)
if not isinstance(e, (ParseError, yaml.YAMLError)):
logger.error(
"Failed to update frontmatter",
path=str(path) if isinstance(path, (str, Path)) else "<unknown>",
error=str(e),
)
raise FileError(f"Failed to update frontmatter: {e}")
+36
View File
@@ -259,6 +259,42 @@ async def test_update_frontmatter_errors(tmp_path: Path):
await update_frontmatter(nonexistent, {"title": "Test"})
@pytest.mark.asyncio
async def test_update_frontmatter_malformed_yaml(tmp_path: Path):
"""Test that malformed YAML frontmatter is handled gracefully (issue #378)."""
test_file = tmp_path / "test.md"
# Create file with malformed YAML frontmatter (colon in title breaks YAML)
malformed_content = """---
title: KB: Something
---
# Test Content
Some content here"""
test_file.write_text(malformed_content, encoding="utf-8")
# Update frontmatter should succeed by treating file as plain markdown
updates = {"type": "note", "tags": ["test"]}
checksum = await update_frontmatter(test_file, updates)
# Verify the update succeeded
assert checksum is not None
updated = test_file.read_text(encoding="utf-8")
# Should have new frontmatter
assert "type: note" in updated
assert "tags:" in updated
assert "- test" in updated
# Original content should be preserved
assert "Test Content" in updated
assert "Some content here" in updated
# Verify the new frontmatter is valid
fm = parse_frontmatter(updated)
assert fm == {"type": "note", "tags": ["test"]}
@pytest.mark.asyncio
def test_sanitize_for_filename_removes_invalid_characters():
# Test all invalid characters listed in the regex