From 404da9d647d141109b98666f2d9c239d03e9168d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 8 Apr 2026 12:13:43 +0000 Subject: [PATCH 1/2] fix(api,worker): catch LiquidJS TokenizationError in HTTP step template compilation Wrap parseAndRender() in try-catch in compileControlValues() for both the API test-http-endpoint and worker execute-http-request-step use cases. When user templates contain invalid Liquid syntax (e.g. unclosed braces like '{subscriber.data.voice'), LiquidJS throws a TokenizationError that was previously unhandled, resulting in a 500 error. Now the original uncompiled values are returned as a graceful fallback. Fixes API-R0 Co-authored-by: Dima Grossman --- .../test-http-endpoint/test-http-endpoint.usecase.ts | 7 ++++++- .../send-message/execute-http-request-step.usecase.ts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts b/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts index 2051b9f5c75..64b5b3c7f35 100644 --- a/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts @@ -140,7 +140,12 @@ export class TestHttpEndpointUsecase { values: Record, context: Record ): Promise { - const compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); + let compiled: string; + try { + compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); + } catch { + return values; + } try { return JSON.parse(compiled); diff --git a/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts index 0c308c4baa6..3c3a2b2604a 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts @@ -288,7 +288,12 @@ export class ExecuteHttpRequestStep extends SendMessageType { values: Record, context: Record ): Promise { - const compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); + let compiled: string; + try { + compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); + } catch { + return values; + } try { return JSON.parse(compiled); From f5a59d01319aeb5f7206f06c416a5136a989ee8c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 9 Apr 2026 07:07:31 +0000 Subject: [PATCH 2/2] fix(api-service,worker): fail HTTP step on invalid Liquid template compilation Remove silent fallback to uncompiled control values when Liquid parse/render fails or JSON.parse of rendered output fails. API test endpoint returns 400; worker records execution failure consistent with other HTTP step errors. Co-authored-by: Dima Grossman --- .../test-http-endpoint.usecase.ts | 20 +++++---- .../execute-http-request-step.usecase.ts | 42 ++++++++++++++----- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts b/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts index 64b5b3c7f35..75425e4721c 100644 --- a/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts +++ b/apps/api/src/app/workflows-v2/usecases/test-http-endpoint/test-http-endpoint.usecase.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { BadRequestException, Injectable } from '@nestjs/common'; import { buildNovuSignatureHeader, GetDecryptedSecretKey, @@ -47,7 +47,14 @@ export class TestHttpEndpointUsecase { const compileContext = this.buildCompileContext(previewPayload); - const compiled = (await this.compileControlValues(controlValues, compileContext)) as typeof controlValues; + let compiled: typeof controlValues; + try { + compiled = (await this.compileControlValues(controlValues, compileContext)) as typeof controlValues; + } catch (cause) { + const message = cause instanceof Error ? cause.message : 'Unknown error'; + + throw new BadRequestException(`HTTP request step template compilation failed: ${message}`); + } const resolvedUrl = (compiled.url as string) ?? ''; const method = (compiled.method as string) ?? 'GET'; @@ -140,17 +147,12 @@ export class TestHttpEndpointUsecase { values: Record, context: Record ): Promise { - let compiled: string; - try { - compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); - } catch { - return values; - } + const compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); try { return JSON.parse(compiled); } catch { - return values; + throw new Error('Rendered template output is not valid JSON'); } } } diff --git a/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts b/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts index 3c3a2b2604a..24e07d82964 100644 --- a/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts +++ b/apps/worker/src/app/workflow/usecases/send-message/execute-http-request-step.usecase.ts @@ -88,10 +88,35 @@ export class ExecuteHttpRequestStep extends SendMessageType { GetDecryptedSecretKeyCommand.create({ environmentId: command.environmentId }) ); - const compiled = (await this.compileControlValues( - controlValuesWithoutSkip, - compileContext - )) as typeof controlValuesWithoutSkip; + let compiled: typeof controlValuesWithoutSkip; + try { + compiled = (await this.compileControlValues( + controlValuesWithoutSkip, + compileContext + )) as typeof controlValuesWithoutSkip; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + await this.createExecutionDetails.execute( + CreateExecutionDetailsCommand.create({ + ...CreateExecutionDetailsCommand.getDetailsFromJob(command.job), + detail: DetailEnum.ACTION_STEP_EXECUTION_FAILED, + source: ExecutionDetailsSourceEnum.INTERNAL, + status: ExecutionDetailsStatusEnum.FAILED, + isTest: false, + isRetry: false, + raw: JSON.stringify({ + error: `HTTP request step template compilation failed: ${errorMessage}`, + }), + }) + ); + + return { + status: SendMessageStatus.FAILED, + errorMessage: DetailEnum.ACTION_STEP_EXECUTION_FAILED, + shouldHalt: !controlValuesWithoutSkip.continueOnFailure, + }; + } const url = compiled.url as string | undefined; const method = (compiled.method as string) ?? 'POST'; @@ -288,17 +313,12 @@ export class ExecuteHttpRequestStep extends SendMessageType { values: Record, context: Record ): Promise { - let compiled: string; - try { - compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); - } catch { - return values; - } + const compiled = await this.liquidEngine.parseAndRender(JSON.stringify(values), context); try { return JSON.parse(compiled); } catch { - return values; + throw new Error('Rendered template output is not valid JSON'); } }