vxcontrol/pentagi · error

failed to stop flow %d: %w

Error message

failed to stop flow %d: %w

What it means

Wrap in flowController.StopFlow when flow.Stop(ctx) fails for a flow found in the in-memory registry. Stop terminates the flow's workers/containers and persists a terminal status; any error during teardown (Docker API failure, DB status update failure) is wrapped with the flow ID.

Source

Thrown at backend/pkg/controller/flows.go:369

	if !ok {
		return nil, ErrFlowNotFound
	}

	return flow, nil
}

func (fc *flowController) StopFlow(ctx context.Context, flowID int64) error {
	fc.mx.Lock()
	defer fc.mx.Unlock()

	flow, ok := fc.flows[flowID]
	if !ok {
		return ErrFlowNotFound
	}

	err := flow.Stop(ctx)
	if err != nil {
		return fmt.Errorf("failed to stop flow %d: %w", flowID, err)
	}

	return nil
}

func (fc *flowController) FinishFlow(ctx context.Context, flowID int64) error {
	fc.mx.Lock()
	defer fc.mx.Unlock()

	flow, ok := fc.flows[flowID]
	if !ok {
		return ErrFlowNotFound
	}

	err := flow.Finish(ctx)
	if err != nil {
		return fmt.Errorf("failed to finish flow %d: %w", flowID, err)
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check the wrapped cause: if it is a Docker error, verify the daemon is running (docker info) and the container state.
  2. If the container is already gone, the flow may be effectively stopped — reconcile the status in the DB manually.
  3. Fix DB connectivity if the wrapped error is a status-update failure.
  4. Retry StopFlow with a longer-lived context after the underlying issue is resolved.
  5. As a last resort, restart the backend so the in-memory registry is rebuilt and orphaned flows are reloaded with their persisted status.

Example fix

// before
ctl.StopFlow(2*time.Second ctx) // canceled mid-teardown -> docker remove fails
// after
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
err := ctl.StopFlow(ctx, flowID)
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the flow exists and docker is reachable before stopping
if _, err := ctl.GetFlow(ctx, flowID); err != nil {
    return err // ErrFlowNotFound
}
if err := dockerClient.Ping(ctx); err != nil {
    return fmt.Errorf("docker daemon unavailable")
}

Try / catch

if err := ctl.StopFlow(ctx, flowID); err != nil {
    if strings.Contains(err.Error(), "failed to stop flow") {
        log.Errorf("stop flow %d: %v", flowID, err)
        // check docker daemon / DB per wrapped cause, then retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling StopFlow with a flowID present in fc.flows where flow.Stop errors — Docker daemon unreachable or the worker container is already gone/removal failed, the DB update to the terminal status fails, or the context is canceled mid-stop.

Common situations: Docker daemon restart while flows are running, containers manually pruned so Stop can't find them, PostgreSQL outage during the status write, or a short-lived request context being canceled during teardown.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/b74aa312299cde5b. Report an issue: GitHub.