Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(mail): invalidate conversation cache on background execution completion

closed#2803CedarCopilot

CedarCopilot wants to merge 1 commit into staging from fix/concentrate-stale-ui-sse

Not deployedTimeline and evidence
  1. Opened
    Sep 16, 2026, 7:07 AM
  2. Sep 16, 2026, 7:26 AM
  3. Merged, live on staging, live on prod, observed
    Pending
  4. Not deployed

    Not deployed

    Pending

Libra has no production signal for this change yet because it has not deployed. Libra checks hourly for 3 days after each deploy.

Summary

A customer reported a deal "splitting into two" in the UI. The real cause: an open conversation went stale after a background-completed execution wrote its fields, and the frontend never learned about it, so the customer was looking at two different data snapshots side by side and mistook the discrepancy for a duplicated deal.

use-aop-refresh-watcher.ts only invalidated the conversation's query by polling for a runId the current session itself had started. It had no way to learn about a write from any other session or trigger , another rep's session, a cron, a webhook, a meeting sync, an HubSpot/Salesforce sync , that touched the same conversation's fields while it was open in this tab.

Fix

  • Adds a new GET /api/conversation-events SSE endpoint (apps/server/src/http/app.ts), mirroring the existing /doc-events pattern, that subscribes to the previously push-only sseEventBus and forwards conversations notifications for the session user.
  • use-aop-refresh-watcher.ts now also subscribes to this SSE channel (in addition to its existing self-triggered poll) and invalidates the conversation's query whenever it sees a conversations notification whose hint matches the open conversation , regardless of which session or trigger produced the write.
  • Extracts the notification's hint format into a new leaf module, apps/server/src/services/sse/conversation-hint.ts (conversationFieldsHint), re-exported through apps/server/src/lib/schemas.ts, so the client and the emitter (apps/server/src/services/crm/conversations.ts) share the exact string format instead of the frontend hand-copying a template string that could drift.
  • Clarifies in apps/server/src/services/sse/event-bus.ts that hint is logging-only for most notification types but is genuinely parsed for routing on conversations notifications.

Test plan

  • pnpm --filter @zero/server run types , clean
  • pnpm --filter @zero/mail run types , clean
  • pnpm deps:check , no dependency violations
  • Manual: open a conversation, trigger a background execution for it from another session/cron, confirm the open tab picks up the field update without a manual refresh

šŸ¤– Generated with Claude Code

Greptile Summary

The PR adds a per-user SSE route and client subscription intended to invalidate open conversation data after externally triggered field updates. The shared hint helper keeps emitter and client routing formats aligned, but the delivery design does not reliably reach all relevant clients.

  • Adds /api/conversation-events and subscribes the conversation watcher through EventSource.
  • Extracts the conversation-field notification hint into a shared leaf module.
  • Retains the existing run-specific polling behavior for locally initiated refreshes.
  • Background worker events cannot reach the API-local subscriber, some authorized viewers are not notified, and disconnected clients can permanently miss events.

Confidence Score: 2/5

The PR is not safe to merge because its API-local subscription cannot receive background worker events, and additional recipient and reconnect gaps still leave open conversations stale.

The primary delivery path terminates at a process-local worker EventEmitter rather than the API service hosting the SSE connection; even reachable emissions omit some authorized viewers and are permanently lost during client disconnects. The unchecked JSON assertion also violates an explicit repository requirement.

Files Needing Attention: apps/server/src/http/app.ts, apps/server/src/services/crm/conversations.ts, apps/mail/modules/crm/hooks/use-aop-refresh-watcher.ts

Important Files Changed

FilenameOverview
apps/server/src/http/app.tsAdds the SSE endpoint, but subscribes to a process-local bus that cannot receive worker
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/hooks/use-aop-refresh-watcher.tsno production surface mapped
  • apps/server/src/http/app.tsno production surface mapped
  • apps/server/src/lib/schemas.tsno production surface mapped
  • apps/server/src/services/crm/conversations.tsno production surface mapped
  • apps/server/src/services/sse/conversation-hint.tsno production surface mapped
  • apps/server/src/services/sse/event-bus.tsno production surface mapped