From 0131b309aab1f47dbf353130cc8a616fb369d32c Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Sun, 30 Aug 2026 14:01:07 +0300 Subject: [PATCH] fix(convex): stop retrying a deleted table and explain it A Convex sync only calls list_snapshot / document_deltas, which answer 404 when a table that schema discovery listed no longer exists at read time. The 404 was unclassified, so every scheduled run retried it and the customer saw the raw driver text (which carries the deployment host). Classify it non-retryable with an actionable message. Cloudflare transients are the 52x/530 family, so a 404 is never a transient blip this could disable a sync over. Generated-By: PostHog Desktop Task-Id: c515c2ae-d0fd-42c5-81fc-44b3650ff0ae --- .../data_imports/sources/convex/source.py | 10 ++++++++++ .../sources/convex/tests/test_convex.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/products/warehouse_sources/backend/temporal/data_imports/sources/convex/source.py b/products/warehouse_sources/backend/temporal/data_imports/sources/convex/source.py index 33d5c7b7a1ba..825aeb922bcf 100644 --- a/products/warehouse_sources/backend/temporal/data_imports/sources/convex/source.py +++ b/products/warehouse_sources/backend/temporal/data_imports/sources/convex/source.py @@ -116,6 +116,16 @@ def get_non_retryable_errors(self) -> dict[str, str | None]: return { "401 Client Error": "Authentication failed. Check your Convex deploy key.", "403 Client Error": "Access denied. Check your Convex deploy key.", + # A sync only calls list_snapshot / document_deltas, and Convex answers those with a 404 + # when the table schema discovery listed is gone at read time (deleted on the source, or a + # component table that isn't served by streaming export). The next scheduled run reissues + # the identical request, so every retry replays the same 404. Cloudflare surfaces transient + # edge problems as the 52x/530 family instead (retried in `_CONVEX_RETRY`), so a 404 is + # never a transient blip that this could disable a sync over. + "404 Client Error": ( + "PostHog couldn't find this table in your Convex deployment. It was likely deleted, so " + "turn off syncing for this table, then re-enable the sync." + ), "StreamingExportNotEnabled": "Streaming export requires the Convex Professional plan. See https://www.convex.dev/plans to upgrade.", # Match a stable substring of the raised message, not the `InvalidWindowError` class name: # the non-retryable check compares against `str(exception)`, which contains the message diff --git a/products/warehouse_sources/backend/temporal/data_imports/sources/convex/tests/test_convex.py b/products/warehouse_sources/backend/temporal/data_imports/sources/convex/tests/test_convex.py index 564ffeacbc9b..ebd6e9da2f31 100644 --- a/products/warehouse_sources/backend/temporal/data_imports/sources/convex/tests/test_convex.py +++ b/products/warehouse_sources/backend/temporal/data_imports/sources/convex/tests/test_convex.py @@ -558,6 +558,11 @@ def test_get_json_schemas_400_with_unparseable_body_falls_through_to_http_error( [ ("401", "401 Client Error: Unauthorized for url: https://x.convex.cloud/api/document_deltas"), ("403", "403 Client Error: Forbidden for url: https://x.convex.cloud/api/document_deltas"), + ( + "missing_table_404", + "404 Client Error: Not Found for url: " + "https://x.convex.cloud/api/list_snapshot?tableName=verification&format=json&component=betterAuth", + ), ( "invalid_window", "Delta cursor for table 'events' is older than Convex's ~30 day retention window. " @@ -569,6 +574,19 @@ def test_known_errors_match(self, _name: str, observed_error: str) -> None: non_retryable_errors = ConvexSource().get_non_retryable_errors() assert any(key in observed_error for key in non_retryable_errors) + def test_missing_table_404_surfaces_actionable_message(self) -> None: + # A deleted table's 404 must stop retrying and tell the customer to turn off syncing for it, + # not store the raw driver text (which carries the deployment host). Mirror the finalizer's + # first-match selection so a reorder that shadowed it with an earlier None key would be caught. + error_msg = ( + "404 Client Error: Not Found for url: " + "https://x.convex.cloud/api/list_snapshot?tableName=verification&format=json&component=betterAuth" + ) + matches = [friendly for key, friendly in ConvexSource().get_non_retryable_errors().items() if key in error_msg] + assert matches, "a missing-table 404 must be classified non-retryable" + assert matches[0] is not None, "a missing-table 404 must surface an actionable message, not raw driver text" + assert "turn off syncing" in matches[0].lower() + @parameterized.expand( [ ("server_error", "500 Server Error for url: https://x.convex.cloud/api/document_deltas"),