From 75b95106bac0ce6d269c5eb91a38b546000753ff Mon Sep 17 00:00:00 2001 From: Chris Bartholomew Date: Thu, 5 Mar 2026 11:07:48 -0500 Subject: [PATCH] fix: use correct schema name in webhook outbox callback to prevent silent transaction rollback (#499) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The retain outbox callback was passing context.tenant_id (raw UUID like 0f3ad4ec-8b88-...) instead of the PostgreSQL schema name (tenant_0f3ad4ec_...). This caused the webhook manager to query a non-existent schema, triggering a PostgreSQL error that silently aborted the entire retain transaction — rolling back all inserted memory data with no clear indication of data loss. Fixed both the async worker path (memory_engine.py) and sync HTTP path (http.py) to use _current_schema.get() which holds the correct tenant-prefixed schema name. Also changed fire_event_with_conn to re-raise exceptions instead of swallowing them, since errors inside a caller's transaction poison it irreversibly. --- hindsight-api/hindsight_api/api/http.py | 4 ++-- hindsight-api/hindsight_api/engine/memory_engine.py | 2 +- hindsight-api/hindsight_api/webhooks/manager.py | 6 +++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/hindsight-api/hindsight_api/api/http.py b/hindsight-api/hindsight_api/api/http.py index c96773d2..cc2be5af 100644 --- a/hindsight-api/hindsight_api/api/http.py +++ b/hindsight-api/hindsight_api/api/http.py @@ -72,7 +72,7 @@ def FieldWithDefault(default_factory: Callable, **kwargs) -> Any: from hindsight_api.config import get_config from hindsight_api.engine.db_utils import acquire_with_retry -from hindsight_api.engine.memory_engine import Budget, _get_tiktoken_encoding, fq_table +from hindsight_api.engine.memory_engine import Budget, _current_schema, _get_tiktoken_encoding, fq_table from hindsight_api.engine.reflect.observations import Observation from hindsight_api.engine.response_models import VALID_RECALL_FACT_TYPES, MemoryFact, TokenUsage from hindsight_api.engine.search.tags import TagsMatch @@ -4280,7 +4280,7 @@ def _register_routes(app: FastAPI): bank_id=bank_id, contents=contents, operation_id=None, - schema=request_context.tenant_id, + schema=_current_schema.get(), ), ) diff --git a/hindsight-api/hindsight_api/engine/memory_engine.py b/hindsight-api/hindsight_api/engine/memory_engine.py index cc39f645..b92c4eae 100644 --- a/hindsight-api/hindsight_api/engine/memory_engine.py +++ b/hindsight-api/hindsight_api/engine/memory_engine.py @@ -594,7 +594,7 @@ class MemoryEngine(MemoryEngineInterface): bank_id=bank_id, contents=contents, operation_id=operation_id, - schema=context.tenant_id, + schema=_current_schema.get(), ), ) diff --git a/hindsight-api/hindsight_api/webhooks/manager.py b/hindsight-api/hindsight_api/webhooks/manager.py index 0379640d..67531c32 100644 --- a/hindsight-api/hindsight_api/webhooks/manager.py +++ b/hindsight-api/hindsight_api/webhooks/manager.py @@ -235,4 +235,8 @@ class WebhookManager: ) except Exception as e: - logger.error(f"Failed to queue webhook deliveries (in-transaction) for event {event.event}: {e}") + logger.error( + f"Failed to queue webhook deliveries (in-transaction) for event {event.event}: {e}. " + "CRITICAL: The enclosing database transaction is now aborted and will roll back all changes." + ) + raise