diff --git a/src/vouch/storage.py b/src/vouch/storage.py index 653ea806..466810bd 100644 --- a/src/vouch/storage.py +++ b/src/vouch/storage.py @@ -193,6 +193,15 @@ def _deserialize_page(text: str) -> Page: return Page(body=body, **meta) +def _load_page_or_skip(path: Path) -> Page | None: + """Parse one page file; skip corrupt/unreadable files like ``_load_or_skip``.""" + try: + return _deserialize_page(path.read_text(encoding="utf-8")) + except (yaml.YAMLError, ValidationError, UnicodeDecodeError, OSError, ValueError) as e: + _log.warning("skipping unreadable page %s: %s", path.name, e) + return None + + class KBStore: """File-backed CRUD layer. Pure I/O — no business logic.""" @@ -550,8 +559,9 @@ def list_pages(self) -> list[Page]: if not pdir.is_dir(): return [] return [ - _deserialize_page(p.read_text(encoding="utf-8")) + pg for p in sorted(pdir.glob("*.md")) + if (pg := _load_page_or_skip(p)) is not None ] # --- entities ---------------------------------------------------------- diff --git a/tests/test_storage.py b/tests/test_storage.py index 03ce0fba..70d2b43e 100644 --- a/tests/test_storage.py +++ b/tests/test_storage.py @@ -860,3 +860,12 @@ def test_list_claims_skips_unreadable_file(store: KBStore) -> None: claims = store.list_claims() assert [c.id for c in claims] == ["c-ok"] + + +def test_list_pages_skips_unreadable_file(store: KBStore) -> None: + """One corrupt page must not take down vouch search/status/lint.""" + store.put_page(Page(id="p-ok", title="ok", body="content")) + (store.kb_dir / "pages" / "p-bad.md").write_text("not valid frontmatter") + + pages = store.list_pages() + assert [p.id for p in pages] == ["p-ok"]