Budibase/budibase · error · JsTimeoutError
JS_TIMEOUT_ERROR
JS_TIMEOUT_ERROR
Error message
Timed out while executing JS
What it means
The JS runner executes user JavaScript inside a VM with an execution timeout. When the underlying VM aborts with 'Script execution timed out.', init converts it into a typed JsTimeoutError (code JS_TIMEOUT_ERROR) so callers can recognize the script ran too long and was terminated instead of crashing with a generic error.
Source
Thrown at packages/server/src/jsRunner/index.ts:48
.withHelpers()
.withBuffer()
.withSnippets(bbCtx?.snippets)
// Persist isolate in context so we can reuse it
if (bbCtx && !bbCtx.vm) {
bbCtx.vm = vm
bbCtx.cleanup = bbCtx.cleanup || []
bbCtx.cleanup.push(() => vm.close())
}
// Because we can't pass functions into an Isolate, we remove them from
// the passed context and rely on the withHelpers() method to add them
// back in.
const { helpers, snippets, ...rest } = ctx
return vm.withContext(rest, () => vm.execute(js))
} catch (error: any) {
if (error.message === "Script execution timed out.") {
throw new JsTimeoutError()
}
throw error
}
})
})
if (env.LOG_JS_ERRORS) {
setOnErrorLog((error: Error) => {
logging.logWarn(
`Error while executing js: ${JSON.stringify(serializeError(error))}`
)
})
}
}
View on GitHub (pinned to a81a902e9a)
Solutions
- Review the executed JS for non-terminating loops or unbounded recursion and fix the termination condition.
- Move heavy data processing out of JS bindings (e.g. into automations or pre-computed data).
- Raise the JS runner timeout configuration if the script legitimately needs more time.
- Break large synchronous work into smaller batches processed across multiple executions.
Example fix
// before
let done = false
while (!done) { /* never sets done */ }
// after
for (let i = 0; i < rows.length; i++) {
if (matches(rows[i])) { result = rows[i]; break }
} Defensive patterns
Strategy: try-catch
Validate before calling
// Static sanity check before running user JS
function hasBoundedLoops(code: string): boolean {
return !/while\s*\(\s*(true|1)\s*\)/.test(code)
} Type guard
function isJsTimeout(err: unknown): err is Error & { code: string } {
return err instanceof Error && (err as { code?: string }).code === "JS_TIMEOUT_ERROR"
} Try / catch
try {
const result = await jsRunner.init(context).execute(js)
} catch (err) {
if (isJsTimeout(err)) {
// mark the binding as timed out, skip or surface a user-facing message
} else { throw err }
} Prevention
- Review user JS for non-terminating loops and unbounded recursion
- Avoid heavy synchronous computation in JS bindings
- Set the VM execution timeout appropriately for expected workloads
- Process large datasets in batches instead of one big script
When it happens
Trigger: Executing a user-defined JS snippet (via the runner's init/execute path, e.g. triggered through publishAsyncEvent) whose script exceeds the configured VM execution timeout — typically infinite loops, unbounded recursion, or very heavy synchronous computation.
Common situations: Automation/JS bindings with a `while` loop that never terminates; accidentally calling the script recursively; transforming a huge dataset synchronously; regression after adding expensive per-row JS logic; timeout configured too low for legitimately heavy scripts.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- JS_REQUEST_TIMEOUT_ERROR
- JS invalid: ${message}
- USER_SCRIPT_ERROR
- Unable to collect response
- JS disabled in environment.
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/109ed06125ff5781.
Report an issue: GitHub.