clockworklabs/SpacetimeDB · critical
JS worker exited before accepting `{ctx}`
Error message
JS worker exited before accepting `{ctx}` What it means
Host-side panic in SpacetimeDB's V8 host (crates/core): a bounded mpsc send of a JS request failed because the receiving JS worker thread has already exited and dropped its channel. The `send_js_request` helper is used for procedure/scheduled-procedure calls into the JavaScript quickjs/v8 runtime; if the worker is dead before accepting, the host cannot proceed and panics with the request context (e.g. `call_procedure`, `scheduled_procedure`). It almost always means the JS worker crashed earlier — its panic is the root cause and appears in logs just before this.
Source
Thrown at crates/core/src/host/v8/mod.rs:745
) -> CallScheduledFunctionResult {
self.send_request("scheduled_procedure", |reply_tx| {
JsProcedureWorkerRequest::ScheduledProcedure { reply_tx, params }
})
.await
}
}
async fn send_js_request<Req, T>(
ctx: &'static str,
tx: &mpsc::Sender<Req>,
request: impl FnOnce(JsReplyTx<T>) -> Req,
) -> T
where
Req: Send + 'static,
{
let (reply_tx, reply_rx) = oneshot::channel();
if tx.send(request(reply_tx)).await.is_err() {
panic!("JS worker exited before accepting `{ctx}`");
}
match reply_rx.await {
Ok(Ok(value)) => value,
Ok(Err(panic)) => panic::resume_unwind(panic),
Err(_) => panic!("JS worker exited before replying to `{ctx}`"),
}
}
async fn send_js_unbounded_request<T>(
ctx: &'static str,
tx: &MeteredUnboundedSender<JsMainWorkerRequest>,
request: impl FnOnce(JsReplyTx<T>) -> JsMainWorkerRequest,
) -> T {
let (reply_tx, reply_rx) = oneshot::channel();
if tx.send(request(reply_tx)).is_err() {
panic!("JS worker exited before accepting `{ctx}`");
}
match reply_rx.await {View on GitHub (pinned to 9e0d92412f)
Solutions
- Look earlier in the host logs for the worker's original crash (uncaught JS exception, OOM, V8 fatal) — fix that first; the context string names which request failed.
- Rebuild and republish the JS module with an SDK version matching the server.
- Wrap risky JS entry points in try/catch so module errors return errors instead of killing the worker.
- If the crash happens during shutdown/deploy races, update the spacetimedb server — worker lifetime bugs around host shutdown are fixed over time.
Example fix
// JS module: before - uncaught throw kills the worker
export function transfer(ctx, from, to, amt) {
const acct = db.account.findByOwner(from);
acct.balance -= amt; // throws if acct undefined
}
// after
export function transfer(ctx, from, to, amt) {
const acct = db.account.findByOwner(from);
if (!acct || acct.balance < amt) throw new Error("insufficient funds"); // handled by host, worker survives
acct.balance -= amt;
db.account.update(acct);
} Defensive patterns
Strategy: validation
Validate before calling
// Host-embedded pattern: the handle exposes is_closed() (tx.is_closed()).
// Guard before sending so a dead worker is handled, not fatal:
if js_instance.is_closed() {
return Err(HostError::js_worker_unavailable("call_procedure"));
}
let ret = js_instance.call_procedure(params).await; Try / catch
// In host code: catch the panic at the call boundary and translate to an error.
let outcome = tokio::task::spawn(async move { js_instance.call_procedure(params).await }).await;
match outcome {
Ok(ret) => Ok(ret),
Err(join_err) if join_err.is_panic() => Err(HostError::js_worker_panicked(join_err.into_panic())),
Err(_) => Err(HostError::js_worker_died),
} Prevention
- Check `is_closed()` on the JS instance before issuing requests when teardown is possible.
- Wrap JS entry points in try/catch so module bugs return errors instead of killing the worker.
- Keep JS module SDK versions aligned with the host; test procedures under load before deploy.
When it happens
Trigger: A JS module procedure panics or calls process.exit-style fatal paths, killing the worker thread; subsequent `call_procedure`/`call_http_handler`/`scheduled_procedure` sends then hit the closed channel. Also possible during host shutdown races where requests are still being routed to a terminated worker.
Common situations: JavaScript quickstart modules throwing uncaught exceptions in reducers/procedures; OOM or V8 fatal errors (stack overflow, invalid wasm); SDK version mismatch between the JS module and the host runtime.
Related errors
- JS worker exited before replying to `{ctx}`
- default export is not a Schema object
- log worker panicked
- must export schema as default export
- snapshot worker panicked
AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20).
Data as JSON: /api/errors/caddec469c7f8e3d.
Report an issue: GitHub.