From e3c1523b5f5bb21003c39498b601d3d98d2c240c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 28 Sep 2026 19:58:45 -0700 Subject: [PATCH 1/2] fix(function): report code-placeholder compiler invariants as server failures Overlapping source edits and exhausted parser-sentinel/Python-marker allocation are broken compiler invariants, not problems in the user's code, but they threw CodePlaceholderCompileError and the Function route answered them with 422 like a user syntax error. They now throw a sibling CodePlaceholderInvariantError, which the route's instanceof check does not match, so it falls through to the existing 500 path. User placeholder and syntax errors keep their 422. --- .../code-placeholders/compiler.test.ts | 23 +++++++++++++++++++ .../execution/code-placeholders/javascript.ts | 3 ++- .../lib/execution/code-placeholders/python.ts | 3 ++- .../lib/execution/code-placeholders/shared.ts | 14 ++++++++++- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index 40c0afde1c0..8554251ed85 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -6,6 +6,11 @@ import { type CodePlaceholderRuntimeBinding, compileCodePlaceholders, } from '@/lib/execution/code-placeholders' +import { + applySourceEdits, + CodePlaceholderCompileError, + CodePlaceholderInvariantError, +} from '@/lib/execution/code-placeholders/shared' import { CodeLanguage } from '@/lib/execution/languages' const installedGlobals = new Set() @@ -1543,3 +1548,21 @@ describe('python true positives survive the dot guard', () => { expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY']) }) }) + +describe('compiler invariant failures', () => { + it('reports overlapping source edits as a compiler invariant, not a user compile error', () => { + const thrown = (() => { + try { + applySourceEdits('return value', [ + { start: 0, end: 6, text: 'yield' }, + { start: 3, end: 9, text: 'x' }, + ]) + } catch (error) { + return error + } + })() + + expect(thrown).toBeInstanceOf(CodePlaceholderInvariantError) + expect(thrown).not.toBeInstanceOf(CodePlaceholderCompileError) + }) +}) diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index 27f503ec93e..5d13939687f 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -2,6 +2,7 @@ import ts from '@typescript/typescript6' import { applySourceEdits, CodePlaceholderCompileError, + CodePlaceholderInvariantError, createCodePlaceholderCompilationContext, isOffsetInRanges, type SourceEdit, @@ -261,7 +262,7 @@ function createSentinel( return sentinel } } - throw new CodePlaceholderCompileError('Unable to allocate a collision-free parser sentinel') + throw new CodePlaceholderInvariantError('Unable to allocate a collision-free parser sentinel') } function createSentinelSource( diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index 2bee46e596f..eb8dc973d0d 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -2,6 +2,7 @@ import { sha256Hex } from '@sim/security/hash' import { applySourceEdits, CodePlaceholderCompileError, + CodePlaceholderInvariantError, createCodePlaceholderCompilationContext, isOffsetInRanges, type SourceEdit, @@ -243,7 +244,7 @@ function createSentinel(code: string): string { const sentinel = `__sim_placeholder_${digest}__` if (!code.includes(sentinel)) return sentinel } - throw new CodePlaceholderCompileError('Unable to allocate a collision-free Python marker') + throw new CodePlaceholderInvariantError('Unable to allocate a collision-free Python marker') } function pythonTriviaGap( diff --git a/apps/sim/lib/execution/code-placeholders/shared.ts b/apps/sim/lib/execution/code-placeholders/shared.ts index 52cd4ea88b4..8390c10b0d1 100644 --- a/apps/sim/lib/execution/code-placeholders/shared.ts +++ b/apps/sim/lib/execution/code-placeholders/shared.ts @@ -27,6 +27,18 @@ export class CodePlaceholderCompileError extends Error { } } +/** + * A broken compiler invariant: Sim's own compilation failed, not the user's code. + * Deliberately not a {@link CodePlaceholderCompileError}, so callers that answer a + * user compile error with a client status treat this as a server failure. + */ +export class CodePlaceholderInvariantError extends Error { + constructor(message: string) { + super(message) + this.name = 'CodePlaceholderInvariantError' + } +} + /** * Scans `{{name}}` placeholders, accepting exactly what `/\{\{([^}]+)\}\}/g` accepts — * a name may contain `{`, because parameter keys are arbitrary strings rather than @@ -270,7 +282,7 @@ export function applySourceEdits(code: string, edits: SourceEdit[]): string { let output = '' for (const edit of sorted) { if (edit.start < cursor || edit.end < edit.start || edit.end > code.length) { - throw new CodePlaceholderCompileError('Overlapping code placeholder transformations') + throw new CodePlaceholderInvariantError('Overlapping code placeholder transformations') } output += code.slice(cursor, edit.start) output += edit.text From 6bbc799feced5c0e02f5293f37b47352c907d3a2 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 28 Sep 2026 21:45:58 -0700 Subject: [PATCH 2/2] fix(function): keep an exhausted JavaScript sentinel space a user compile error User code can occupy every sentinel a short placeholder could take, so running out of them is the code's fault, not a compiler invariant. --- .../code-placeholders/compiler.test.ts | 36 +++++++++++-------- .../execution/code-placeholders/javascript.ts | 3 +- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index 8554251ed85..65fe05ef593 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1549,20 +1549,28 @@ describe('python true positives survive the dot guard', () => { }) }) -describe('compiler invariant failures', () => { +describe('compiler failure classification', () => { it('reports overlapping source edits as a compiler invariant, not a user compile error', () => { - const thrown = (() => { - try { - applySourceEdits('return value', [ - { start: 0, end: 6, text: 'yield' }, - { start: 3, end: 9, text: 'x' }, - ]) - } catch (error) { - return error - } - })() - - expect(thrown).toBeInstanceOf(CodePlaceholderInvariantError) - expect(thrown).not.toBeInstanceOf(CodePlaceholderCompileError) + const applyOverlappingEdits = () => + applySourceEdits('return value', [ + { start: 0, end: 6, text: 'yield' }, + { start: 3, end: 9, text: 'x' }, + ]) + + expect(applyOverlappingEdits).toThrow(CodePlaceholderInvariantError) + expect(applyOverlappingEdits).not.toThrow(CodePlaceholderCompileError) + }) + + it('reports an exhausted JavaScript sentinel space as a user compile error', async () => { + /** Every `$xyz$` sentinel a five-character `{{a}}` could take, packed under the code cap. */ + const alphabet = [...'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'] + const payloads = alphabet.flatMap((a) => + alphabet.flatMap((b) => alphabet.map((c) => `${a}${b}${c}`)) + ) + const code = `const taken = '$${payloads.join('$')}$'\nreturn {{a}}` + + await expect( + compileCodePlaceholders({ code, language: CodeLanguage.JavaScript, params: { a: 1 } }) + ).rejects.toBeInstanceOf(CodePlaceholderCompileError) }) }) diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index 5d13939687f..27f503ec93e 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -2,7 +2,6 @@ import ts from '@typescript/typescript6' import { applySourceEdits, CodePlaceholderCompileError, - CodePlaceholderInvariantError, createCodePlaceholderCompilationContext, isOffsetInRanges, type SourceEdit, @@ -262,7 +261,7 @@ function createSentinel( return sentinel } } - throw new CodePlaceholderInvariantError('Unable to allocate a collision-free parser sentinel') + throw new CodePlaceholderCompileError('Unable to allocate a collision-free parser sentinel') } function createSentinelSource(