fix(call-recorder): listen proper updated fileds event (#23135)

## Context

Around meeting-end peaks (~6pm), Recall/Svix delivers event bursts for
every recorded call across the 700+ workspaces the app is installed on.
Each delivery was processed synchronously in the API request path, and
internal failures surfaced to Svix as non-2xx, so it redelivered — a
self-feeding storm of 500s and latency that only stopped when the
webhook endpoint was disabled.

## What changed

With #23134, server-route dispatch defaults to **queued** server-side:
the API acks Svix with a 202 right after signature verification,
`process-recall-webhook` runs on the worker queue, and failed runs retry
there (resolver `retryLimit`, default 3). The resolver needs no change
at all — `recall-webhook.ts` is back to main, and no SDK update is
required.

Remaining app changes:
- `schedule-recall-bot-on-call-recording-update` declares
`updatedFields` (the pending-transition fields) on its
`callRecording.updated` trigger, so the server drops the app's own
scheduling-progress and artifact writes **before** spawning a full
execution instead of executing and returning "skipped". The in-handler
check stays as a fallback.
- Version bumped to 1.5.0.
- Code comments introduced by earlier revisions of this PR removed per
review.

## Tests

- New test pins the trigger's `updatedFields` declaration.
- `yarn test:unit` (507 tests), `yarn lint`, `yarn typecheck` all green.

## Notes

- The `call-recorder (dockerhub-latest)` CI leg fails because main
already requires `twenty >= 2.23.0` while the latest published image is
2.22.0 — pre-existing, clears when 2.23.0 images publish.
- The 250s `import-call-recording-artifacts` route still runs in an API
request slot behind the fire-and-forget own-route POST; moving it fully
off the request path is a follow-up.
This commit is contained in:
martmull
2026-07-22 14:58:25 +02:00
committed by GitHub
parent f59bda1dbf
commit e0debf87a7
5 changed files with 20 additions and 4 deletions
@@ -1,6 +1,6 @@
{
"name": "@twentyhq/call-recorder",
"version": "1.4.0",
"version": "1.5.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
@@ -1,6 +1,8 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { scheduleRecallBotOnCallRecordingUpdateHandler } from 'src/logic-functions/schedule-recall-bot-on-call-recording-update';
import scheduleRecallBotOnCallRecordingUpdateLogicFunction, {
scheduleRecallBotOnCallRecordingUpdateHandler,
} from 'src/logic-functions/schedule-recall-bot-on-call-recording-update';
const queryMock = vi.hoisted(() => vi.fn());
const mutationMock = vi.hoisted(() => vi.fn());
@@ -130,6 +132,21 @@ describe('scheduleRecallBotOnCallRecordingUpdateHandler', () => {
vi.restoreAllMocks();
});
it('declares the pending-transition fields on the trigger so the server drops other updates', () => {
expect(
scheduleRecallBotOnCallRecordingUpdateLogicFunction.config
.databaseEventTriggerSettings,
).toEqual({
eventName: 'callRecording.updated',
updatedFields: [
'recordingRequestStatus',
'status',
'externalBotId',
'calendarEventId',
],
});
});
it('schedules a bot when an update clears the bot id of a requested recording', async () => {
stubPendingCallRecordingQueries();
@@ -178,7 +178,6 @@ const queueCallRecordingArtifactsImport = async ({
};
};
// A throw bubbles to a non-2xx so Svix redelivers; the preceding status update re-applies idempotently.
const requestCallRecordingArtifactsImportOrThrow = async ({
callRecordingId,
}: {
@@ -5,7 +5,6 @@ import { PROCESS_RECALL_WEBHOOK_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/
import { handleRecallWebhook } from 'src/logic-functions/flows/handle-recall-webhook.util';
import { type RecallWebhookBody } from 'src/logic-functions/recall-api/parse-recall-webhook-event.util';
// Dispatched by the recall-webhook resolver; runs in the resolved workspace so the client is workspace-scoped.
export const processRecallWebhookHandler = (body: RecallWebhookBody) =>
handleRecallWebhook({
client: new CoreApiClient(),
@@ -125,5 +125,6 @@ export default defineLogicFunction({
handler: scheduleRecallBotOnCallRecordingUpdateHandler,
databaseEventTriggerSettings: {
eventName: `${CALL_RECORDING_OBJECT_NAME}.updated`,
updatedFields: PENDING_TRANSITION_FIELDS,
},
});