From adf03284ff798cd726804d90222f74151b77186b Mon Sep 17 00:00:00 2001 From: Chris McDonough Date: Mon, 1 Jun 2026 08:07:43 -0400 Subject: [PATCH] Classify IsADirectoryError and NotADirectoryError as PermanentError Both are OSError subclasses caught by the broad timeout/io handler and classified as transient. Pointing at a directory instead of a file or a broken path component will never succeed on retry. --- haiku_rag_slim/haiku/rag/ingester/workers/pipeline.py | 3 +++ tests/ingester/test_pipeline.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/haiku_rag_slim/haiku/rag/ingester/workers/pipeline.py b/haiku_rag_slim/haiku/rag/ingester/workers/pipeline.py index 20f018ab..08acc2c5 100644 --- a/haiku_rag_slim/haiku/rag/ingester/workers/pipeline.py +++ b/haiku_rag_slim/haiku/rag/ingester/workers/pipeline.py @@ -63,6 +63,9 @@ def _classify(exc: BaseException) -> Exception: if isinstance(exc, PermissionError): return PermanentError(f"permission denied: {exc}") + if isinstance(exc, IsADirectoryError | NotADirectoryError): + return PermanentError(f"path error: {exc}") + if isinstance(exc, asyncio.TimeoutError | TimeoutError | OSError): return TransientError(f"timeout/io: {exc}") diff --git a/tests/ingester/test_pipeline.py b/tests/ingester/test_pipeline.py index 218a9d26..de17829a 100644 --- a/tests/ingester/test_pipeline.py +++ b/tests/ingester/test_pipeline.py @@ -279,3 +279,13 @@ async def test_permission_error_classified_as_permanent(): client.create_document_from_source.side_effect = PermissionError("no access") with pytest.raises(PermanentError, match="permission denied"): await run_job(client, _job()) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("exc_class", [IsADirectoryError, NotADirectoryError]) +async def test_directory_errors_classified_as_permanent(exc_class): + """Pointing at a directory instead of a file should DLQ immediately.""" + client = _mock_client() + client.create_document_from_source.side_effect = exc_class("not a file") + with pytest.raises(PermanentError, match="path error"): + await run_job(client, _job())