Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 7dc2158484 fix: add workspace scoping to migration runner timer labels to prevent collisions
https://sonarly.com/issue/17529?type=bug

TwentyCRM login is completely blocked because the Docker entrypoint's `yarn command:prod upgrade` runs workspace migrations synchronously before starting the server. While migrations are running, the server process never starts, so all GraphQL queries (including `GetPublicWorkspaceDataByDomain`) hang indefinitely.
2026-03-23 14:40:11 +00:00
2 changed files with 15 additions and 9 deletions
@@ -313,14 +313,16 @@ export abstract class BaseWorkspaceMigrationRunnerActionHandlerService<
label: string;
method: () => Promise<void>;
}): Promise<void> {
const uniqueLabel = `${this.actionType}_${this.metadataName} ${label} #${Math.random().toString(36).slice(2, 8)}`;
this.logger.time(
'BaseWorkspaceMigrationRunnerActionHandlerService',
`${this.actionType}_${this.metadataName} ${label}`,
uniqueLabel,
);
await method();
this.logger.timeEnd(
'BaseWorkspaceMigrationRunnerActionHandlerService',
`${this.actionType}_${this.metadataName} ${label}`,
uniqueLabel,
);
}
}
@@ -108,13 +108,15 @@ export class WorkspaceMigrationRunnerService {
async invalidateCache({
allFlatEntityMapsKeys,
workspaceId,
runId,
}: {
allFlatEntityMapsKeys: (keyof AllFlatEntityMaps)[];
workspaceId: string;
runId: string;
}): Promise<void> {
this.logger.time(
'Runner',
`Cache invalidation ${allFlatEntityMapsKeys.join()}`,
`Cache invalidation ${allFlatEntityMapsKeys.join()} ${runId}`,
);
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
@@ -147,7 +149,7 @@ export class WorkspaceMigrationRunnerService {
this.logger.timeEnd(
'Runner',
`Cache invalidation ${allFlatEntityMapsKeys.join()}`,
`Cache invalidation ${allFlatEntityMapsKeys.join()} ${runId}`,
);
}
@@ -163,8 +165,10 @@ export class WorkspaceMigrationRunnerService {
allFlatEntityMaps: AllFlatEntityMaps;
metadataEvents: MetadataEvent[];
}> => {
this.logger.time('Runner', 'Total execution');
this.logger.time('Runner', 'Initial cache retrieval');
const runId = `#${Math.random().toString(36).slice(2, 8)}`;
this.logger.time('Runner', `Total execution ${runId}`);
this.logger.time('Runner', `Initial cache retrieval ${runId}`);
const queryRunner =
externalQueryRunner ?? this.coreDataSource.createQueryRunner();
@@ -195,7 +199,7 @@ export class WorkspaceMigrationRunnerService {
flatMapsKeys: allFlatEntityMapsKeys,
});
this.logger.timeEnd('Runner', 'Initial cache retrieval');
this.logger.timeEnd('Runner', `Initial cache retrieval ${runId}`);
const { flatApplicationMaps } =
await this.workspaceCacheService.getOrRecompute(workspaceId, [
@@ -217,7 +221,7 @@ export class WorkspaceMigrationRunnerService {
});
}
this.logger.time('Runner', 'Transaction execution');
this.logger.time('Runner', `Transaction execution ${runId}`);
if (!isTransactionAlreadyActive) {
await queryRunner.connect();
@@ -254,7 +258,7 @@ export class WorkspaceMigrationRunnerService {
await queryRunner.commitTransaction();
}
this.logger.timeEnd('Runner', 'Transaction execution');
this.logger.timeEnd('Runner', `Transaction execution ${runId}`);
await this.invalidateCache({
allFlatEntityMapsKeys,