Classify PermissionError as PermanentError instead of TransientError

PermissionError is a subclass of OSError, so it was caught by the
broad timeout/io handler and classified as transient. An unreadable
file would retry 5 times then DLQ — permissions don't fix themselves
without operator intervention.

Add an explicit PermissionError check before the OSError catch so
unreadable files go straight to the DLQ.
This commit is contained in:
Chris McDonough 2026-06-01 07:54:08 -04:00 committed by Yiorgis Gozadinos
parent 4372b0546d
commit 3c7dbf0966
No known key found for this signature in database
2 changed files with 12 additions and 0 deletions

View file

@ -60,6 +60,9 @@ def _classify(exc: BaseException) -> Exception:
if isinstance(exc, FileNotFoundError):
return PermanentError(f"file not found: {exc}")
if isinstance(exc, PermissionError):
return PermanentError(f"permission denied: {exc}")
if isinstance(exc, asyncio.TimeoutError | TimeoutError | OSError):
return TransientError(f"timeout/io: {exc}")

View file

@ -270,3 +270,12 @@ async def test_file_not_found_classified_as_permanent():
client.create_document_from_source.side_effect = FileNotFoundError("gone")
with pytest.raises(PermanentError, match="file not found"):
await run_job(client, _job())
@pytest.mark.asyncio
async def test_permission_error_classified_as_permanent():
"""An unreadable file should go straight to the DLQ, not retry."""
client = _mock_client()
client.create_document_from_source.side_effect = PermissionError("no access")
with pytest.raises(PermanentError, match="permission denied"):
await run_job(client, _job())