Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 83af7a26f4 fix(billing): avoid loading relations in canFeatureBeUsed check
https://sonarly.com/issue/33412?type=bug

The `canFeatureBeUsed` billing check called during every workflow trigger execution runs an expensive query with two levels of JOINed relations, but only checks if a subscription exists. Under worker load, this query times out at the 10-second `query_timeout` threshold, failing workflow runs.

Fix: ## What changed

Modified `canFeatureBeUsed()` in `billing-usage.service.ts` to use a lightweight direct query instead of calling `getCurrentBillingSubscription()`.

### Before
`canFeatureBeUsed` called `billingSubscriptionService.getCurrentBillingSubscription({ workspaceId })` which runs a `find()` query with two levels of eagerly-loaded relations:
- `billingSubscriptionItems` (JOIN to `billingSubscriptionItem` table)
- `billingSubscriptionItems.billingProduct` (JOIN to `billingProduct` table)

All of this just to check `!!billingSubscription` — a boolean existence check that doesn't use any of the loaded relations.

### After
`canFeatureBeUsed` now queries `billingSubscriptionRepository.findOne()` directly with:
- The same `where` filter: `{ workspaceId, status: Not(SubscriptionStatus.Canceled) }`
- `select: ['id']` — only fetches the primary key column
- No relations loaded

This eliminates the two JOINs and reduces the query from a multi-table join returning all columns to a simple single-table lookup returning one column. The partial unique index `IDX_BILLING_SUBSCRIPTION_WORKSPACE_ID_UNIQUE` on `(workspaceId) WHERE status IN ('trialing', 'active', 'past_due')` can be used efficiently.

### Why this is safe
- `billingSubscriptionRepository` is already injected in `BillingUsageService` (line 51-52) and used by other methods in the same class
- `Not` and `SubscriptionStatus` were already imported in this file
- The filter logic is identical to `getCurrentBillingSubscription`
- The return value semantics are unchanged: truthy if a non-canceled subscription exists, falsy otherwise
- The two callers (`workflow-runner.workspace-service.ts` and `billing-usage-event.listener.ts`) both only check the boolean result
2026-05-01 20:13:54 +00:00
@@ -4,7 +4,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { type Repository } from 'typeorm';
import { Not, type Repository } from 'typeorm';
import { ClickHouseService } from 'src/database/clickHouse/clickHouse.service';
import { formatDateTimeForClickHouse } from 'src/database/clickHouse/clickHouse.util';
@@ -62,8 +62,9 @@ export class BillingUsageService {
}
const billingSubscription =
await this.billingSubscriptionService.getCurrentBillingSubscription({
workspaceId,
await this.billingSubscriptionRepository.findOne({
where: { workspaceId, status: Not(SubscriptionStatus.Canceled) },
select: ['id'],
});
return !!billingSubscription;