Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 7f9e2a70d9 fix: move IMAP folder sync outside database transaction in processAccount
https://sonarly.com/issue/18767?type=bug

The `SaveImapSmtpCaldavAccount` mutation fails with `QueryRunnerAlreadyReleasedError` because external IMAP folder discovery (slow network I/O) runs inside a database transaction, causing the PostgreSQL connection to be terminated during the idle period.

Fix: Moved createMessageChannelService.createMessageChannel() and createCalendarChannelService.createCalendarChannel() outside the workspaceDataSource.transaction() block in ImapSmtpCalDavAPIService.processAccount(). The transaction now only wraps connectedAccountDataAccessService.save().

This eliminates the window where a PG connection sits idle in a transaction while IMAP folder discovery performs multiple network round trips (connect to mail server, list folders, check UID validity per folder). That idle window was causing the PG connection to be terminated by idle_in_transaction_session_timeout, leading to QueryRunnerAlreadyReleasedError when TypeORM attempted to COMMIT.

This is safe because WorkspaceEntityManager.save() always creates its own query runner via createQueryRunnerForEntityPersistExecutor() and releases it in .finally() — it never actually participated in the outer transaction. The change preserves identical data flow while eliminating the timeout risk.
2026-03-26 16:52:43 +00:00
@@ -106,27 +106,36 @@ export class ImapSmtpCalDavAPIService {
},
manager,
);
if (shouldCreateMessageChannel) {
await this.createMessageChannelService.createMessageChannel({
workspaceId,
connectedAccountId: newOrExistingAccountId,
handle,
manager,
});
}
if (shouldCreateCalendarChannel) {
await this.createCalendarChannelService.createCalendarChannel({
workspaceId,
connectedAccountId: newOrExistingAccountId,
handle,
manager,
});
}
},
);
// Channel creation is intentionally outside the transaction above.
// createMessageChannel triggers IMAP folder discovery (network I/O to
// the mail server) which can take tens of seconds. Holding an open DB
// transaction during that time causes the PG connection to be
// terminated for idle-in-transaction timeout, resulting in
// QueryRunnerAlreadyReleasedError (see Sentry #18767).
// This is safe because WorkspaceEntityManager.save() creates its own
// query runner internally and does not participate in the outer
// transaction anyway.
if (shouldCreateMessageChannel) {
await this.createMessageChannelService.createMessageChannel({
workspaceId,
connectedAccountId: newOrExistingAccountId,
handle,
manager: workspaceDataSource.manager,
});
}
if (shouldCreateCalendarChannel) {
await this.createCalendarChannelService.createCalendarChannel({
workspaceId,
connectedAccountId: newOrExistingAccountId,
handle,
manager: workspaceDataSource.manager,
});
}
return newOrExistingAccountId;
},
authContext,