BloopAI/vibe-kanban · warning · ContainerError

Child process not found for execution

Error message

Child process not found for execution

What it means

stop_execution fetches the live child process handle from the execution's store via get_child_from_store and errors when no child is registered for the execution id. A child only exists while the process is running and tracked; after exit, cleanup, or if it never attached, there is nothing to stop. This is an expected consequence of stopping an already-finished or unknown execution.

Source

Thrown at crates/local-deployment/src/container.rs:1414

        }

        // Spawn unified exit monitor: watches OS exit and optional executor signal
        let hn = self.spawn_exit_monitor(&execution_process.id, spawned.exit_signal);
        self.add_exit_monitor_handle(execution_process.id, hn).await;

        Ok(())
    }

    async fn stop_execution(
        &self,
        execution_process: &ExecutionProcess,
        status: ExecutionProcessStatus,
    ) -> Result<(), ContainerError> {
        let child = self
            .get_child_from_store(&execution_process.id)
            .await
            .ok_or_else(|| {
                ContainerError::Other(anyhow!("Child process not found for execution"))
            })?;
        let exit_code = if status == ExecutionProcessStatus::Completed {
            Some(0)
        } else {
            None
        };

        ExecutionProcess::update_completion(&self.db.pool, execution_process.id, status, exit_code)
            .await?;

        // Try graceful cancellation first, then force kill
        if let Some(cancel) = self.take_cancellation_token(&execution_process.id).await {
            cancel.cancel();

            // Wait for exit monitor to finish gracefully
            if let Some(monitor_handle) = self.take_exit_monitor_handle(&execution_process.id).await
            {
                match tokio::time::timeout(Duration::from_secs(5), monitor_handle).await {

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Check the execution's status first and skip the stop call if it is already Completed/Failed/Killed.
  2. Treat this error as benign and continue — the goal (process not running) is already achieved.
  3. Avoid double-stopping: track stopped execution ids in the caller or make stop idempotent.
  4. If after a server restart, the child cannot be stopped in-memory; rely on process-status reconciliation instead.

Example fix

// before
container.stop_execution(&process, ExecutionProcessStatus::Killed).await?;
// after
if matches!(process.status, ExecutionProcessStatus::Running | ExecutionProcessStatus::Paused) {
    container.stop_execution(&process, ExecutionProcessStatus::Killed).await?;
}
Defensive patterns

Strategy: type-guard

Validate before calling

if !matches!(process.status, ExecutionProcessStatus::Running) {
    return Ok(()); // nothing to stop
}

Type guard

fn is_stoppable(status: &ExecutionProcessStatus) -> bool {
    matches!(status, ExecutionProcessStatus::Running | ExecutionProcessStatus::Paused)
}

Try / catch

match container.stop_execution(&proc, ExecutionProcessStatus::Killed).await {
    Err(ContainerError::Other(e)) if e.to_string().contains("Child process not found") => Ok(()), // already gone
    other => other,
}

Prevention

When it happens

Trigger: Calling stop_execution (directly or via kill_all_running_processes, stop_execution_process, delete_workspace, start_dev_server, archive_workspace, try_stop) with an execution id whose child was already reaped, was never tracked, or whose MsgStore was cleared.

Common situations: Double-stop of the same execution; stopping a process that already exited; delete_workspace/archive racing with the process finishing naturally; restarting the server so in-memory children were lost while DB rows remain.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/9bc06bbae42e8cc0. Report an issue: GitHub.