vxcontrol/pentagi · info · ErrFlowAlreadyStopped

flow already stopped

Error message

flow already stopped

What it means

ErrFlowAlreadyStopped is the public sentinel error indicating the target flow is already in a stopped/finished state, so a stop operation is redundant. Returned by flow stop paths; match with errors.Is. It signals an idempotency conflict rather than a hard failure.

Source

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

	"fmt"
	"sort"
	"sync"
	"time"

	"pentagi/pkg/config"
	"pentagi/pkg/database"
	"pentagi/pkg/docker"
	"pentagi/pkg/graph/subscriptions"
	"pentagi/pkg/providers"
	"pentagi/pkg/providers/provider"
	"pentagi/pkg/tools"

	"github.com/sirupsen/logrus"
)

var (
	ErrFlowNotFound       = fmt.Errorf("flow not found")
	ErrFlowAlreadyStopped = fmt.Errorf("flow already stopped")
)

type FlowController interface {
	CreateFlow(
		ctx context.Context,
		userID int64,
		input string,
		prvname provider.ProviderName,
		prvtype provider.ProviderType,
		functions *tools.Functions,
		resources []database.UserResource,
	) (FlowWorker, error)
	CreateAssistant(
		ctx context.Context,
		userID int64,
		flowID int64,
		input string,
		useAgents bool,

View on GitHub (pinned to ea665308ba)

Solutions

  1. Treat it as success/idempotent: the flow is stopped either way — check errors.Is(err, controller.ErrFlowAlreadyStopped) and continue.
  2. Disable the stop action in the UI once flow status is finished/stopped.
  3. Serialize stop operations or use per-flow locking if concurrent stops are common.
  4. Verify current flow status via GetFlow before stopping.

Example fix

// before
if err := fc.StopFlow(ctx, flowID); err != nil {
	return fmt.Errorf("stop flow: %w", err)
}
// after
if err := fc.StopFlow(ctx, flowID); err != nil && !errors.Is(err, controller.ErrFlowAlreadyStopped) {
	return fmt.Errorf("stop flow: %w", err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

var status string
err := db.QueryRow("SELECT status FROM flows WHERE id=$1", flowID).Scan(&status)
// skip StopFlow if status is already finished/stopped

Type guard

func IsFlowAlreadyStopped(err error) bool {
	return errors.Is(err, controller.ErrFlowAlreadyStopped)
}

Try / catch

err := fc.StopFlow(ctx, flowID)
if err != nil && !IsFlowAlreadyStopped(err) {
	return fmt.Errorf("stop flow: %w", err)
}
// ErrFlowAlreadyStopped is treated as idempotent success

Prevention

When it happens

Trigger: Calling StopFlow (or related termination APIs) on a flow that was already stopped/finished — e.g., double-clicking a stop button, retries after a timeout, or concurrent stop requests.

Common situations: Duplicate stop requests from the UI; retry logic re-issuing a stop that actually succeeded; two operators stopping the same flow concurrently.

Related errors


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