Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(crm): conversation status/context/synced-state follow the active deal

merged#2528CedarCopilot

CedarCopilot wants to merge 0 commits into staging from fix/crm-active-deal-status-and-task-channel

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 20, 2026, 12:50 PM
  2. Merged
    Aug 20, 2026, 1:25 PM
  3. Live on prod
    Aug 20, 2026, 1:25 PM
  4. Observed 35 hours
    Aug 20, 2026, 1:25 PM
  5. Pipelines steady after this deploy
    Aug 20, 2026, 1:25 PM
  6. Unobserved

    Live on prod, no production signal yet

    Aug 21, 2026, 11:57 PM

Behaviors Libra is checking

Chat workflow behavior from context-formatting/conversation-context.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 1 prod failure log lines for [MEETING_NOTES_WEBHOOK] Error processing webhook (AskElephant webhook payload must contain at least one attendee with a valid email address), but this domain-wide failure family is not tied to this intent.

prod, checked Aug 21, 2026, 10:57 PM
CRM integration behavior from components/VirtualizedTableRow.tsx, crm/crm-field-sync.ts keeps working in prod.Inconclusivelow confidence

Strict CloudWatch fallback found 84 recent prod failure log lines for [UpdateExternalCrmWorkflow] Driver update failed (Step updateOpportunityField failed: HTTP 400: [{\), but the same failure had 79 log lines in the comparable pre-deploy baseline. Libra is not blaming this PR.

prod, checked Aug 21, 2026, 11:57 PM

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.

Problem

On a conversation carrying several linked CRM deals (a company with a live deal plus old closed ones), several code paths ignored the "conversation follows its open deal" rule and let a terminal sibling win. Surfaced auditing Peter (Pirros): his open HGA - 5 deal (stage Scoping, correctly active:true in metadata) rendered under a Lost card because a closed sibling on the same conversation had stamped status = Lost - Company. Same class as the Cordogan Clark meeting-notes skip (gating read the wrong deal).

Root cause

The per-event CRM sync wrote crm_conversations.status from whichever deal triggered the event, not the conversation's active deal. A closed sibling's sync (or the agent field-update path) therefore clobbered the open deal's stage , last-writer-wins across the collapsed deals. The active-deal marking in integration_metadata was correct; only the status column (which the pipeline board groups by) diverged. Meanwhile the agent saw N indistinguishable <linked_crm_deal> blocks with no signal of which was authoritative.

What changed

  • status follows the active deal , new shared guard dropStatusWriteFromNonActiveDeal drops the status write when the incoming deal isn't the conversation's active one (pickActiveExternalCrmDeal). Applied at both sync entry points:
    • applyFullProgrammaticCrmMappings (handleExecuteExternalCrm + conversation-sync / initial-sync / auto-link / pull-refresh)
    • applyCrmFieldSync (agent field-update path) , also keeps the stage changeKey reported handled so the agent won't re-apply the closed deal's stage.
    • No-op for single-deal conversations; no-op for pull-refresh (which already passes the active deal).
  • Agent context , formatIntegrationMetadataForAgent now marks the active deal and each deal's open/closed state when a conversation has 2+ linked deals, so subagents (gating, crm-updater, notes) reason off the right deal.
  • Frontend , VirtualizedTableRow derives isCrmSynced from the active deal (pickActiveDeal) so the synced badge and the shown deal come from one source.

How to test

crm_conversation_updates / agent_tool_calls should show [crm-field-sync] Skipped status write ... is not the active deal ... when a closed sibling of a multi-deal conversation syncs. Repro tests cover both sync paths (open + closed sibling on one conversation, closed deal syncs → status stays on the active deal's stage).

Verification

  • vitest crm-field-sync-full (5/5), active-deal + summary (28/28)
  • pnpm --filter @zero/server run types → clean
  • pnpm deps:check → no violations

Not included (deploy-lag, already on staging)

The related Haworth Tompkins task-creation failure is not in this PR , it was the multi-action task_channel being rejected by the constraint that migration 0059_retire_multi_action_channel.sql (PR #2526, already on staging) tightened. Prod code behind that deploy still wrote multi-action; it resolves on deploy. Nothing to add here.

šŸ¤– Generated with Claude Code

Greptile Summary

This PR makes CRM conversation status, agent context, and the frontend synced indicator follow the selected active deal rather than an arbitrary linked sibling.

  • Adds a shared guard to suppress status writes from non-active deals across both CRM synchronization paths.
  • Annotates multi-deal agent context with active and open/closed state.
  • Aligns the pipeline-row synced badge with the deal selected for display.
  • Adds regression coverage for active and closed sibling synchronization.

Confidence Score: 4/5

The cross-provider identity ambiguity should be fixed before merging because it can allow a non-active terminal deal to overwrite conversation status.

The new guard compares provider-scoped deal IDs without retaining the event provider, while the repository's active-deal reconciliation explicitly treats pro

Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/mail/modules/crm/components/VirtualizedTableRow.tsxno production surface mapped
  • apps/server/src/mastra/utils/context-formatting/conversation-context.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/crm-field-sync-full.test.tsno production surface mapped
  • apps/server/src/services/crm/crm-field-sync.tsno production surface mapped