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
- Check the wrapped cause: if it is a Docker error, verify the daemon is running (docker info) and the container state.
- If the container is already gone, the flow may be effectively stopped — reconcile the status in the DB manually.
- Fix DB connectivity if the wrapped error is a status-update failure.
- Retry StopFlow with a longer-lived context after the underlying issue is resolved.
- 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
- Use a generous timeout context for Stop; teardown involves Docker + DB calls.
- Keep the Docker daemon healthy and avoid pruning containers of running flows.
- Monitor DB health; Stop persists a terminal status.
- After a failed stop, verify the container state manually before retrying.
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
- failed to finish flow %d: %w
- flow %d has status %s: loading aborted: %w
- failed to renew flow %d status: %w
- failed to load flow %d: %w
- failed to get flow %d status: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/b74aa312299cde5b.
Report an issue: GitHub.