fix(flow): serialize per-phone execution to prevent concurrent flows

Two messages arriving close together could start two parallel flow
executions for the same phone. The second would create a new session
while the first was mid-AI-block, causing duplicate greetings and
race conditions. Per-phone async lock ensures sequential execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 20:48:55 +05:30
parent d819888351
commit f1c026cf7a

View File

@@ -44,9 +44,28 @@ export class FlowExecutionService {
this.auth = apiKey ? `Bearer ${apiKey}` : '';
}
// Per-phone lock to prevent concurrent flow executions
private readonly locks = new Map<string, Promise<void>>();
async handleMessage(message: NormalizedMessage): Promise<void> {
const { phone } = message;
// Serialize executions per phone — prevent two concurrent flows
const existing = this.locks.get(phone);
const execute = async () => {
if (existing) await existing.catch(() => {});
await this._handleMessage(message);
};
const promise = execute();
this.locks.set(phone, promise);
await promise.finally(() => {
if (this.locks.get(phone) === promise) this.locks.delete(phone);
});
}
private async _handleMessage(message: NormalizedMessage): Promise<void> {
const { phone } = message;
// 1. Load existing session or start new flow
let session = await this.sessions.load(phone);
let flow: Flow | null = null;