From 1b469168c80faabc1ce32aef919bec484379b13e Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 21 Apr 2026 01:00:28 +0200 Subject: [PATCH] chore(workflow): temporarily lift credit-cap gate on workflow steps (#19904) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Removes the per-step `canBillMeteredProduct(WORKFLOW_NODE_EXECUTION)` gate in `WorkflowExecutorWorkspaceService.executeStep` so workflows keep running when a workspace reaches `hasReachedCurrentPeriodCap`. Previously every step failed with `BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE` (\"No remaining credits to execute workflow…\"). - Drops the now-unused `BillingService` injection, related imports, and the helper `canBillWorkflowNodeExecution`. Updates the spec to drop the corresponding billing-validation case and mock. - Leaves the constant file and `BillingService` itself in place, plus a TODO at the previous gate site, so the behavior can be re-enabled with a small, reviewable revert. ## Notes - Usage events are still emitted (`USAGE_RECORDED` / `UsageResourceType.WORKFLOW`), and `EnforceUsageCapJob` keeps computing the cap and flipping `hasReachedCurrentPeriodCap` — only the executor stops consulting that flag. - The runner-level `canFeatureBeUsed` check in `WorkflowRunnerWorkspaceService.run` was already log-only (subscription presence, not credits), so no change there. - AI chat (`agent-chat.resolver.ts`) keeps its own `BILLING_CREDITS_EXHAUSTED` gate; this PR does not touch it. ## Test plan - [x] `npx jest workflow-executor.workspace-service.spec.ts` (17/17 pass) - [ ] Manual: with billing enabled and the metered subscription item flagged `hasReachedCurrentPeriodCap = true`, trigger a workflow run and verify steps execute end-to-end instead of failing with the billing error. Made with [Cursor](https://cursor.com) --- ...orkflow-executor.workspace-service.spec.ts | 44 ------------------- .../workflow-executor.workspace-service.ts | 25 ++--------- 2 files changed, 3 insertions(+), 66 deletions(-) diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/__tests__/workflow-executor.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/__tests__/workflow-executor.workspace-service.spec.ts index 98530e865d..750278d97c 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/__tests__/workflow-executor.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/__tests__/workflow-executor.workspace-service.spec.ts @@ -2,8 +2,6 @@ import { Test, type TestingModule } from '@nestjs/testing'; import { getWorkflowRunContext, StepStatus } from 'twenty-shared/workflow'; -import { BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE } from 'src/engine/core-modules/billing/constants/billing-workflow-execution-error-message.constant'; -import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; @@ -71,11 +69,6 @@ describe('WorkflowExecutorWorkspaceService', () => { getWorkflowRunOrFail: jest.fn(), }; - const mockBillingService = { - isBillingEnabled: jest.fn().mockReturnValue(true), - canBillMeteredProduct: jest.fn().mockReturnValue(true), - }; - const mockExceptionHandlerService = { captureExceptions: jest.fn(), }; @@ -108,10 +101,6 @@ describe('WorkflowExecutorWorkspaceService', () => { provide: WorkflowRunWorkspaceService, useValue: mockWorkflowRunWorkspaceService, }, - { - provide: BillingService, - useValue: mockBillingService, - }, { provide: ExceptionHandlerService, useValue: mockExceptionHandlerService, @@ -341,39 +330,6 @@ describe('WorkflowExecutorWorkspaceService', () => { ); }); - it('should stop when billing validation fails', async () => { - mockBillingService.isBillingEnabled.mockReturnValueOnce(true); - mockBillingService.canBillMeteredProduct.mockReturnValueOnce(false); - - await service.executeFromSteps({ - workflowRunId: mockWorkflowRunId, - stepIds: ['step-1'], - workspaceId: mockWorkspaceId, - }); - - expect(workflowActionFactory.get).toHaveBeenCalledTimes(0); - - expect( - workflowRunWorkspaceService.updateWorkflowRunStepInfo, - ).toHaveBeenCalledTimes(1); - - expect(workflowRunWorkspaceService.endWorkflowRun).toHaveBeenCalledTimes( - 1, - ); - - expect( - workflowRunWorkspaceService.updateWorkflowRunStepInfo, - ).toHaveBeenCalledWith({ - stepId: 'step-1', - stepInfo: { - error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE, - status: StepStatus.FAILED, - }, - workflowRunId: mockWorkflowRunId, - workspaceId: 'workspace-id', - }); - }); - it('should not emit billing event for skipped steps', async () => { (shouldExecuteStep as jest.Mock).mockReturnValue(false); (shouldSkipStepExecution as jest.Mock).mockReturnValue(true); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts index d036667c48..133f793231 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service.ts @@ -9,9 +9,6 @@ import { WorkflowRunStepInfos, } from 'twenty-shared/workflow'; -import { BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE } from 'src/engine/core-modules/billing/constants/billing-workflow-execution-error-message.constant'; -import { BillingProductKey } from 'src/engine/core-modules/billing/enums/billing-product-key.enum'; -import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; @@ -59,7 +56,6 @@ export class WorkflowExecutorWorkspaceService { private readonly workflowActionFactory: WorkflowActionFactory, private readonly workspaceEventEmitter: WorkspaceEventEmitter, private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService, - private readonly billingService: BillingService, private readonly exceptionHandlerService: ExceptionHandlerService, private readonly metricsService: MetricsService, @InjectMessageQueue(MessageQueue.workflowQueue) @@ -376,16 +372,6 @@ export class WorkflowExecutorWorkspaceService { ); } - private async canBillWorkflowNodeExecution(workspaceId: string) { - return ( - !this.billingService.isBillingEnabled() || - (await this.billingService.canBillMeteredProduct( - workspaceId, - BillingProductKey.WORKFLOW_NODE_EXECUTION, - )) - ); - } - private async processStepExecutionResult({ actionOutput, stepId, @@ -467,14 +453,9 @@ export class WorkflowExecutorWorkspaceService { workflowRunId: string; workspaceId: string; }) { - const canBill = await this.canBillWorkflowNodeExecution(workspaceId); - - if (!canBill) { - return { - error: BILLING_WORKFLOW_EXECUTION_ERROR_MESSAGE, - }; - } - + // TODO: re-enable workflow node execution credit cap once billing limits are revisited. + // Previously gated on BillingService.canBillMeteredProduct(WORKFLOW_NODE_EXECUTION); + // temporarily disabled so workflows keep running when the period cap is reached. const stepId = step.id; const workflowAction = this.workflowActionFactory.get(step.type);