Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(meetings): hydrate a meeting event when a later delivery carries the transcript

merged#2598CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/meeting-transcript-hydrate

Live on prod, no production signal yetTimeline and evidence
  1. Opened
    Aug 26, 2026, 5:34 PM
  2. Aug 26, 2026, 5:48 PM
  3. Merged
    Aug 26, 2026, 7:02 PM
  4. Live on prod
    Aug 26, 2026, 7:02 PM
  5. Observed 2 days
    Aug 26, 2026, 7:02 PM
  6. Pipelines steady after this deploy
    Aug 26, 2026, 7:02 PM
  7. Unobserved

    Live on prod, no production signal yet

    Aug 29, 2026, 9:25 AM

Behaviors Libra is checking

Later deliveries of an already-executed meeting can fill missing transcript, notes, AI notes, recording metadata, and duration on the stored meeting event without re-running meeting agents or duplicating recap, draft, or CRM actions.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Aug 29, 2026, 8:25 AM
A failure while attempting to hydrate a duplicate meeting delivery is treated as non-fatal so the otherwise valid deduplicated meeting request does not fail.Inconclusivelow confidence

Verification reached its 130s outer budget before the verifier returned. Libra is keeping this intent verifying until the next check.

prod, checked Aug 29, 2026, 9:23 AM
Meeting re-deliveries only add fields that are missing from the stored event, so empty or less-complete deliveries cannot erase an existing transcript, recording, notes, or duration.Inconclusivelow confidence

Unable to complete verification: telemetry shows 47 handle_execute_meeting spans across 23 users with no recorded exceptions, but the hydration log query was not reliably scoped (it returned millions of likely false matches), and available logs do not establish that complete.

prod, checked Aug 29, 2026, 9:25 AM

Libra has verdicts on 0 of 3 tracked behaviors on prod; 3 are still being checked. Libra checks hourly for 3 days after each deploy.

The bug

Meeting-notes providers re-send the same externalId repeatedly, and the early deliveries are often empty , the recorder registers the meeting when its notetaker joins, then attaches the transcript minutes after the call ends.

findOrCreateMeetingEvent's existing-row branch updated conversationId and returned:

if (existingRow) {
  eventId = existingRow.meetingEvent.eventId;
  if (existingRow.event.conversationId !== conversationId) { … }
  return { eventId, isNew: false, meetingEvent: existingRow.meetingEvent };
}

Nothing else was ever written. The first delivery won permanently, and every later payload carrying the real transcript was silently dropped.

What it did in production

Observed on Fellow, 2026-08-26, from the API logs (payload sizes are the S3 writes of each POST):

T7Ptbz1cHb   16:15 → 17:00    1,177 bytes   empty shell, ~14 deliveries
             17:05 → 17:30   31,866 bytes   full transcript, 7 deliveries , all discarded

gCGQPMqXOC   16:45 → 17:25    1,669 bytes   empty shell
             18:10 → 18:35   51,330 bytes   full transcript , all discarded

Fellow finished the transcript at 17:04:06, four minutes after a meeting that ended at 17:00. Cedar received it seven times and stored none of it.

Every one of that user's 135 meeting rows had updated_at == created_at , nothing had ever been updated after creation. 21 were left permanently transcript-less. Downstream, the wiki-maintainer was staging proposals off meetings with no content, which is how the customer noticed.

The transcripts were never lost

storeMeetingInS3 writes every re-delivery, and transcriptionKey is just ${connectionId}/${meetingId}.json. 18 of those 21 have a full transcript in S3 today (19,317,60,056 chars). Only the row's pointer and flags were missing, because this branch never wrote them. Repair is a metadata update, not a re-fetch.

The fix

Hydrate on re-delivery, upgrade-only: a field is written only when the incoming payload has it and the stored row does not.

That direction is load-bearing. Empty and full deliveries interleave, so an unconditional overwrite would let a late empty payload erase a transcript already held. Covers transcript, ai notes, notes, recording and duration, and returns the updated row rather than the stale one the caller would otherwise get.

Provider-agnostic on purpose. Fellow surfaced it, but any recorder that re-sends the same externalId , Granola, Circleback, Fathom , hits this same branch.

Verification

  • pnpm --filter @zero/server run types clean
  • pnpm deps:check clean (1572 modules, no violations)
  • eslint clean on both changed files
  • 5 new tests pinning the decision table, including "never erases a transcript we already hold" and the no-op case when the stored row is already complete

Related, not in this PR

The upstream Superglue tool (fellow-meetings-webhook-sync) now rejects transcript-less payloads in buildMeetings, so the pre-meeting shells no longer reach Cedar at all. That was applied and verified separately against live Fellow data. For Fellow this PR is now belt-and-braces; for every other recorder it is the fix.

🤖 Generated with Claude Code

Greptile Summary

This PR hydrates existing meeting-event rows when later provider deliveries contain transcript, notes, recording, or duration data, while avoiding replacement of already-present content.

  • Adds upgrade-only hydration and returns the updated meeting row.
  • Adds five decision-table tests for richer, empty, and already-complete redeliveries.
  • The aggregate presence guards still leave partially populated transcript or recording metadata unfillable.

Confidence Score: 4/5

The partial-metadata hydration defect should be fixed before merging because later deliveries can still leave transcript or recording fields permanently absent.

The new branch repairs completely

Show production surfaces and changed-file mapping

Production surfaces

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

Changed files → surfaces

  • apps/server/src/mastra/routeHandlers/event-execution/handleExecuteMeeting.tsno production surface mapped
  • apps/server/src/services/crm/__tests__/meeting-event-hydration.test.tsno production surface mapped
  • apps/server/src/services/crm/meeting-events.tsno production surface mapped