Hmbown/CodeWhale · warning

Runtime is shutting down; execution admission is closed

Error message

Runtime is shutting down; execution admission is closed

What it means

ensure_accepting_execution refuses new execution admissions once the Runtime's cancellation token is cancelled, i.e. during shutdown. This prevents work from being enqueued into a Runtime that is tearing down.

Solutions

  1. Stop submitting work once shutdown is initiated; await Runtime completion instead
  2. Re-create a new Runtime instance if you need to continue executing work
  3. Check the cancellation/shutdown state before admitting work in your orchestration code

Example fix

// before
runtime.submit_turn(turn).await;
// after
if !runtime.is_shutting_down() {
    runtime.submit_turn(turn).await;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if runtime.is_cancelled() { /* skip submission */ }

Try / catch

match runtime.submit_work(task) {
    Err(e) if e.to_string().contains("execution admission is closed") => {
        // runtime is shutting down; drop or reschedule on a new runtime
    }
    other => other?,
}

Prevention

When it happens

Trigger: Submitting a turn, tool call, or other execution to a Runtime whose shutdown has begun (cancel_token cancelled).

Common situations: Firing a background task that races Runtime shutdown; automation scripts submitting work while the app is quitting; long-lived handle used after drop of the main session.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/175c1a797906a33e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/runtime_threads.rs:5256

        }
        self.engine_workers
            .lock()
            .retain(|(_, worker)| !completion_finished(worker));
        let mut active = self.active.lock().await;
        active.engines.clear();
        active.lru.clear();
        failure.map_or(Ok(()), Err)
    }

    fn track_receipt_worker(&self, worker: tokio::task::JoinHandle<()>) {
        let mut monitors = self.turn_monitors.lock();
        monitors.retain(|monitor| !completion_finished(monitor));
        monitors.push(retained_completion(worker));
    }

    fn ensure_accepting_execution(&self) -> Result<()> {
        if self.cancel_token.is_cancelled() {
            bail!("Runtime is shutting down; execution admission is closed");
        }
        Ok(())
    }

    /// Runtime clones retained by live Engine/tool work retain this lease too.
    /// A TaskManager cancellation timeout cannot release execution ownership.
    pub(crate) fn retain_task_execution_lease(
        &self,
        lease: Arc<crate::task_manager::TaskExecutionLease>,
    ) -> Result<()> {
        let mut current = self.task_execution_lease.lock();
        if current.is_some() {
            bail!("This Runtime already owns a task execution manager");
        }
        *current = Some(lease);
        Ok(())
    }

View on GitHub (pinned to 73e0f67d83)