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

  1. 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.
  2. Rebuild and republish the JS module with an SDK version matching the server.
  3. Wrap risky JS entry points in try/catch so module errors return errors instead of killing the worker.
  4. 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

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


AI-assisted analysis of clockworklabs/SpacetimeDB@9e0d92412f (2026-08-20). Data as JSON: /api/errors/caddec469c7f8e3d. Report an issue: GitHub.