You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Self-host runs execute in QuickJS on the server's main thread. apps/host-selfhost/src/execution.ts wires makeQuickJsExecutor() with defaults, runtime-quickjs evaluates with the sync WASM build, and the only bound is the in-sandbox interrupt handler at timeoutMs = 300 000. So a CPU-bound script owns the Node event loop until it finishes or hits the 5 min deadline. Nothing else on the instance is served in the meantime.
Measured on the official v1.5.40 image, Linux amd64, one MCP client:
execute with while (Date.now() - t0 < 150000) {} returned 200 after 150.3 s.
during those 150 s, /api/health took 3.6 s and a concurrent GET /api/oauth/clients got no answer within 30 s. The web UI did not load either.
same script at 75 s: same behaviour, returned 200 after 75.6 s.
I/O-bound scripts are fine — every tools.* call yields to the host, and since #1437 the deadline is suspended while a dispatch is in flight — so normal agent code never triggers this. It takes one careless loop (spin-poll on a job, an accidental O(n²) over a big payload) to stall a shared instance for up to five minutes for every user and every MCP client on it.
What works today, and why it is not enough
The 5 min interrupt eventually kills the script. That is a ceiling, not isolation.
Speed up and stabilize CI e2e #1551 adds EXECUTOR_SANDBOX_TIMEOUT_MS to self-host. I'll set it to ~60 s the day it ships; it shortens the stall, it does not remove it.
Cloud does not have the problem because it runs code in dynamic workers, isolated from the API process. runtime-deno-subprocess exists in the repo but no app depends on it.
Ask
Run the self-host sandbox off the main thread: a worker_threads worker around the existing sync QuickJS build would be enough (or the asyncify variant with a periodic yield, or the Deno subprocess runtime). Keep the interrupt deadline as the backstop. Merging #1551 with a lower default than 5 min would help in the meantime.
Not asking for per-execution CPU accounting or resource quotas here — just that one execute cannot take the whole server with it.
The situation
Self-host runs
executein QuickJS on the server's main thread.apps/host-selfhost/src/execution.tswiresmakeQuickJsExecutor()with defaults,runtime-quickjsevaluates with the sync WASM build, and the only bound is the in-sandbox interrupt handler attimeoutMs= 300 000. So a CPU-bound script owns the Node event loop until it finishes or hits the 5 min deadline. Nothing else on the instance is served in the meantime.Measured on the official
v1.5.40image, Linux amd64, one MCP client:executewithwhile (Date.now() - t0 < 150000) {}returned 200 after 150.3 s./api/healthtook 3.6 s and a concurrentGET /api/oauth/clientsgot no answer within 30 s. The web UI did not load either.I/O-bound scripts are fine — every
tools.*call yields to the host, and since #1437 the deadline is suspended while a dispatch is in flight — so normal agent code never triggers this. It takes one careless loop (spin-poll on a job, an accidental O(n²) over a big payload) to stall a shared instance for up to five minutes for every user and every MCP client on it.What works today, and why it is not enough
EXECUTOR_SANDBOX_TIMEOUT_MSto self-host. I'll set it to ~60 s the day it ships; it shortens the stall, it does not remove it.runtime-deno-subprocessexists in the repo but no app depends on it.Ask
Run the self-host sandbox off the main thread: a
worker_threadsworker around the existing sync QuickJS build would be enough (or the asyncify variant with a periodic yield, or the Deno subprocess runtime). Keep the interrupt deadline as the backstop. Merging #1551 with a lower default than 5 min would help in the meantime.Not asking for per-execution CPU accounting or resource quotas here — just that one
executecannot take the whole server with it.