Conversation
…ail connectors Wrap DynamicErasureEmailConnector.get_config() to include the connector key in validation errors, so users can identify which connector is misconfigured. Also improve resilience of the batch email send and needs_batch_email_send flows: handle per-connector errors so one broken connector doesn't block others, always requeue successfully-processed requests, and treat misconfigured connectors as needing email to fail visibly rather than silently skipping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The per-connector error handling refactor inadvertently moved requeue_privacy_requests_after_email_send before the failure check, causing privacy requests to advance toward completion even when email sends failed. Move requeue after the has_failure guard to preserve the original behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The batch service now catches exceptions per-connector instead of letting them propagate. Update integration tests to assert EmailExitState.email_send_failed instead of pytest.raises, since errors are still surfaced via privacy request status and execution logs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review: Improve error messages for misconfigured dynamic erasure email connectors
The goals here are solid — surfacing the connector key in Pydantic validation errors, making send_email_batch process connectors independently so one failure doesn't block others, and routing misconfigured connectors into the visible error path. The approach is good, but there is one critical runtime bug introduced by the refactor.
Critical (must fix)
requeue_privacy_requests_after_email_send is called after the session is closed (see inline comment on line 118 of email_batch_service.py).
The with self.get_new_session() as session: block closes at line 114. Lines 115–119 are outside it, so on the success path, session is already closed and privacy_requests is a Query bound to that closed session. requeue_privacy_requests_after_email_send iterates the query and calls privacy_request.save(db=db) — this will raise a DetachedInstanceError at runtime on every successful batch send.
The fix is to move the if has_failure: check and the requeue_privacy_requests_after_email_send(...) call back inside the with self.get_new_session() as session: block. The original code had this in the correct place inside the try: block.
Note: the integration tests for the failure path mock requeue_privacy_requests_after_email_send, which means this regression won't be caught by those tests. Only a successful end-to-end run would surface it.
Suggestions
- Broad
except Exception(inline on line 90): transient infrastructure errors are now silently absorbed instead of allowing Celery to retry. Worth documenting as a known trade-off, or narrowing the catch for certain error types. "third_party_vendor_name"assertion (inline ontest_dynamic_erasure_email_connector.pyline 290): couples the test to Pydantic's internalValidationErrorformatting — consider removing it since the surrounding assertions already cover the intent.- Missing test for
needs_batch_email_sendexception path: the newexcept Exceptionblock inrequest_runner_service.py(lines 1174–1183) that treats check errors as "needing email" has no unit test. A test verifying that an exception duringneeds_email()still returnsTruewould prevent regressions.
- Move has_failure check and requeue call back inside session scope to fix pre-existing closed-session bug - Narrow except clause from broad Exception to (MessageDispatchException, DynamicErasureEmailConnectorException) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
galvana
left a comment
There was a problem hiding this comment.
This is a nice set of improvements to the error handling. I spent some time going back and for with Claude about what would happen if email connector A succeeds and B fails. As I understand it, today we would just re-send the email for connector A the next time the email batch kicks off. This is a problem that existed before your current change so we don't have to fix it now, but it's something to think about.
When erasure email connector A succeeds but connector B fails, all privacy requests are set to error. On retry (after operator reset), connector A would re-send emails it already sent. This adds a check for existing complete ExecutionLogs per connector before sending, preventing duplicate emails on retry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ticket ENG-2489
Description Of Changes
When a dynamic erasure email connector is incorrectly configured (e.g., empty secrets), the error message currently shows a generic Pydantic validation error with no indication of which connector is broken. This makes debugging difficult for end users.
This PR improves error handling across the dynamic erasure email flow to always identify the misconfigured connector by key.
Code Changes
DynamicErasureEmailConnector.get_config()to catchValidationErrorand re-raise with the connector key in the messagesend_email_batchso one broken connector doesn't block othersneeds_batch_email_sendto fail visibly rather than silently skipSteps to Confirm
Dynamic erasure email connector 'my_connector' is incorrectly configured: ...)Pre-Merge Checklist
CHANGELOG.mdupdated🤖 Generated with Claude Code