CedarCopilot wants to merge 3 commits into staging from fix/seed-crm-fields-on-first-link
Live on prod, no production signal yet
Libra has verdicts on 0 of 7 tracked behaviors on prod; 7 are still being checked. Libra checks hourly for 3 days after each deploy.
When a CRM deal is linked to a conversation, Cedar never applies its CRM to Cedar field
mappings, so crm_conversations.status stays NULL for any deal that was already at its
final stage the first time Cedar saw it.
The mechanism, in order:
crm-discover-new-deals.ts:234 (and the hourly background sync behind it) calls
executeExternalCrmAgentExecutionDirect({ ..., conversationId: undefined }) , by
definition, a deal with no conversation yet.handleExecuteExternalCrm.ts:359 gates the programmatic mapping step on
if (conversationId). With no conversation it is skipped.assignEventToConversationTool.ts:1246), and the post-execution self-heal link at
handleExecuteExternalCrm.ts:617 re-links it , but neither applies field mappings.
linkExternalCrmEventToConversation has zero references to them, and its details
argument carries only dealStage as a display string, not a deal snapshot the mapper
could read.status is NULL permanently.The pile of approval cards asking a rep to approve a stage the CRM already holds is the
agent correctly noticing an empty status. It is downstream of this, not the cause.
92 conversations across 18 users / 8 orgs have a linked deal whose integration_metadata
carries a real dealStage while crm_conversations.status is NULL , Pirros 35, Corgi 25,
Getdynasty 13, AspireIQ 12, the rest in ones and twos.
Not all 92 are this bug, and the split matters for the backfill:
dealstage to status and the stage label
matches an AOP status option.status field mapping at all on the connection (Corgi's two users, 25
conversations; Dynasty; parts of AspireIQ). Configuration, not code.Plan Review,
Appointment Scheduled, Closed Won - Renewal), so status-match resolves to null.A representative case: Pirros conversation be9d87b5 (ODA - 1), created 2026-08-31, deal
15172683409 linked the same day already isClosed: true at Lost - Company, one
completed external_crm execution, no events since, status still NULL.
syncCedarFieldsFromCrmSnapshot (crm-pull-refresh.ts) is the "resolve the effective
mappings, apply the whole snapshot, swallow failures" triple every call site needs,
extracted so the error handling exists once. handleExecuteExternalCrm now applies it in
three places, all via one local seedFieldsOnFirstLink helper that no-ops on a conversation
the pre-step already seeded:
externalCrmEventHasConversation which conversation now holds the deal and seeds that.
Without it a newly discovered terminal-stage deal could stay unseeded permanently on any
run that linked and then failed. (Greptile P1, fixed in dd3a769b1.)The seed applies the FULL mapping set, not just the stage. On a real conversation that
is 28 fields , status, closeDate, dealValue, name, segment, firm_size, sql_date, dq_reason, type_of_firm, drawing_software, product_modules… , and it renames a
domain-named conversation (svdesign.com) to the CRM's deal name (SV Design - 1). This
is deliberate: it is the same set every other CRM to Cedar path applies, the CRM is
author
Libra has not measured any production surfaces for this change yet.