Limits
Set resource budgets for sandbox execution and host function traffic.
Limits
Limits bound the time, memory, data, and host interaction available to one invocation. They protect the host from accidental overuse and from source that tries to consume unbounded resources.
Concept
Every invocation has a RunLimits budget. The budget applies to the complete
invocation, including guest execution, host function calls, result
serialization, and continuation operations.
The defaults are a starting point, not a policy. A short data transformation and a coding agent rarely want the same budget.
Defaults
| Limit | Default | Bounds |
|---|---|---|
timeoutMs | 30 seconds | The complete invocation |
memoryLimitBytes | 64 MiB | QuickJS and sync-bridge admission |
maxStackSizeBytes | 2 MiB | QuickJS stack |
maxSourceBytes | 256 KiB | Entry and loaded module source |
maxResultBytes | 1 MiB | Serialized result data |
maxConsoleOutputBytes | 64 KiB | Guest console output |
maxHostFunctionArgumentsBytes | 1 MiB | Serialized arguments for one call |
maxHostFunctionOutputBytes | 4 MiB | Serialized binding or loader output |
maxBridgeRequests | 256 | Binding and module requests |
maxInFlightBridgeRequests | 32 | Host function calls active at once |
maxContinuationBytes | 32 MiB | Serialized continuation state |
Every configured value must be a positive integer.
Implementation
Use createRunner() when several invocations should share the same policy:
import { createRunner } from 'run';
const MiB = 1024 * 1024;
const runner = createRunner({
limits: {
timeoutMs: 15_000,
memoryLimitBytes: 32 * MiB,
maxStackSizeBytes: 1024 * 1024,
maxSourceBytes: 128 * 1024,
maxResultBytes: 512 * 1024,
maxConsoleOutputBytes: 32 * 1024,
maxHostFunctionArgumentsBytes: 256 * 1024,
maxHostFunctionOutputBytes: MiB,
maxBridgeRequests: 100,
maxInFlightBridgeRequests: 8,
maxContinuationBytes: 8 * MiB,
},
});Every runner.run() call inherits these values:
const result = await runner.run({
source,
hostFunctions,
});An individual invocation can override only the values that need to change:
const result = await runner.run({
source,
hostFunctions,
limits: {
timeoutMs: 5_000,
maxResultBytes: 128 * 1024,
},
});The five-second timeout and 128 KiB result budget replace the runner defaults for this invocation. Every other limit continues to come from the runner.
The convenience run() function also accepts per-invocation limits:
import { run } from 'run';
const result = await run({
source,
hostFunctions,
limits: {
timeoutMs: 5_000,
memoryLimitBytes: 32 * 1024 * 1024,
},
});Selection
Start with the defaults and measure representative workloads before changing them. Lower a limit when the workload has a predictable shape, and raise it only when the application is prepared to absorb the additional resource use.
Source, argument, output, result, and continuation limits are measured after their relevant UTF-8 encoding or serialization step. A JavaScript object that appears small can have a much larger serialized representation.
For modules, maxSourceBytes applies both before and after type stripping, and
maxHostFunctionOutputBytes applies to the JSON-encoded loader response written
to the bridge. Escapes such as quotes and backslashes can make that encoded
response larger than the source text.
Memory and worker concurrency should be planned together. Raising
memoryLimitBytes increases the maximum memory available to every active
worker, which may reduce the number of invocations a process can safely run at
once.
Runs without synchronous bindings or a module loader do not allocate a
synchronous bridge. When needed, the bridge is included in process memory
admission, must fit within memoryLimitBytes, and has a hard 64 MiB capacity.
maxBridgeRequests controls total asynchronous bindings, synchronous bindings,
and module-loader interaction, including requests rejected by the worker's
name or argument size checks. Catching those errors does not restore the budget.
Existing continuations created before aggregate attempt accounting retain their
original counting rules. New continuations bind maxBridgeRequests and
maxHostFunctionArgumentsBytes so those limits must match when resuming.
Each module normalization and load counts as a separate request, while
maxInFlightBridgeRequests controls parallel host interaction. Lowering the
in-flight limit can protect a database or external service even when the total
number of asynchronous host function calls remains unchanged. Synchronous and
module requests are serialized separately and do not count toward the in-flight
limit.
Failures
Limit violations reject the invocation. The most relevant stable codes are:
RUN_TIMEOUTindicates that the invocation exceededtimeoutMs.RUN_SOURCE_TOO_LARGEindicates that the source exceededmaxSourceBytes.RUN_BRIDGE_LIMITindicates that total or in-flight host function traffic exceeded its configured limit.RUN_HOST_FUNCTION_ERRORcan indicate that host function arguments exceeded the host boundary or that a host function request was otherwise invalid.RUN_SERIALIZATION_ERRORindicates that a transferred value was not serializable or exceeded another serialization boundary.RUN_PROTOCOL_ERRORcan indicate that continuation state or a continuation token exceededmaxContinuationBytes.
QuickJS memory and stack exhaustion do not have dedicated stable error codes. Treat their failures as runtime failures, monitor them, and adjust the budget only after confirming that the source is behaving as expected.
Use RunError.isInstance() and inspect error.code when the application needs
different responses:
import { RunError } from 'run';
try {
await runner.run({ source, hostFunctions });
} catch (error) {
if (RunError.isInstance(error)) {
console.error('Run failed', {
code: error.code,
details: error.details,
});
}
throw error;
}Do not automatically retry a limit violation with a larger budget. First determine whether the source is behaving as expected and whether host side effects are safe to repeat.