CedarCopilot wants to merge 0 commits into staging from fix/sherlock-preexec-contact-dup-abort-20260818-112553
Live on prod, no production signal yet
OTEL fallback found no prod spans matching findOrCreateContact since deploy.
OTEL fallback found no prod spans matching findOrCreateMeetingEvent since deploy.
OTEL fallback found no prod spans matching GitHub Actions CI / Lint JS since deploy.
OTEL fallback found no prod spans matching findOrCreateContact since deploy.
OTEL fallback found no prod spans matching findOrCreateMeetingEvent since deploy.
Libra has verdicts on 0 of 2 tracked behaviors on prod; 2 are still being checked. Libra checks hourly for 3 days after each deploy.
Event-driven agent runs (drafting, wiki maintainer, crm-updater) were silently aborting at pre-execution-setup whenever linking a meeting/email participant as a contact hit the partial UNIQUE(user_id, person_id) index (uq_crm_contacts_user_person) , i.e. any time a participant is a person the user already has a contact for under a different email (very common: varun@/varun.lahoti@, @company/@other-domain aliases).
findOrCreateContact has a recovery path for exactly this violation, but it only inspected err.constraint / err.constraint_name. Under the Supabase pgbouncer transaction pooler, the violation surfaces with an empty/absent constraint field, so:
namedPersonUq → false ('' !== 'uq_crm_contacts_user_person')unnamed23505 → false ('' == null is false)pre-execution-setup treats any create/link error as fatal, killing the whole run before a single trigger executed.154bc0dd-11b9-4258-9daf-468a9dede7c1 (a real user's meeting): pre-execution-setup returned success:false, "Failed to create/link events: duplicate key value violates unique constraint \"uq_crm_contacts_user_person\""; output empty; no wiki proposal generated.WHERE person_id IS NOT NULL) and all of the user's contacts carry a non-null person_id, so every re-contact of a known person trips it.services/crm/contacts.ts , normalize an empty-string constraint to null and, when the driver/pooler doesn't expose a usable constraint name, fall back to matching uq_crm_contacts_user_person inside err.message. The person-UQ recovery now fires regardless of how the error is surfaced. A named constraint that isn't the person UQ still re-throws (unchanged safety); a truly nameless 23505 still recovers by SQLSTATE elimination (unchanged).services/crm/meeting-events.ts , link participant/organizer contacts best-effort: each link is independent and its failure is logged and skipped. The meeting event is already persisted before this; a contact write can no longer abort event creation. This mirrors the existing best-effort external_crm linking + Turbopuffer upload in the same path.Shared CRM services → affects all users/orgs. This is intentional: the fatal-abort behavior was hitting any account with multi-email participants, silently. The recovery change only widens when we recover (guarded by personId + the existing "UPDATE must match a row, else re-throw" check); the meeting-events change only makes a previously-fatal side-effect non-fatal.
ode.ai, conversation ecbeac31-f9fe-4413-b6a4-95912911fcaf, meeting event c09265f1-…) after deploy and confirm pre-execution-setup succeeds and the wiki-maintainer proposal generates.agent_tool_calls, pre-execution-setup should return success:true for events whose participants collide on (user_id, person_id).crm.contacts.personUqRecovery.unnamed warns (should remain rare) and the new non-fatal Failed to link contact … (non-fatal) errors (should be near-zero once recovery works).🤖 Fixed by Sherlock
The PR prevents duplicate participant contacts from aborting event-driven agent runs and makes meeting participant relationship writes best-effort.
Libra has not measured any production surfaces for this change yet.