vxcontrol/pentagi · warning · ErrFlowNotFound

flow not found

Error message

flow not found

What it means

ErrFlowNotFound is the public sentinel error returned by GetFlow, StopFlow, FinishFlow, RenameFlow, DeleteFlow, and PatchAssistant when no flow with the given ID exists for the caller. It is a fixed message ('flow not found'), intended to be matched with errors.Is rather than string comparison. Callers should treat it as a 404-class condition.

Source

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

	"errors"
	"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,

View on GitHub (pinned to ea665308ba)

Solutions

  1. Verify the flowID via GetFlow or the flows list before mutating operations.
  2. After a server restart, wait for LoadFlows to finish or re-fetch the flow list.
  3. Handle it as 404 in the API layer and refresh client state.
  4. Check errors.Is(err, controller.ErrFlowNotFound) instead of string matching.

Example fix

// before
flow, err := fc.GetFlow(ctx, flowID)
if err != nil {
	return fmt.Errorf("get flow: %w", err)
}
// after
flow, err := fc.GetFlow(ctx, flowID)
if err != nil {
	if errors.Is(err, controller.ErrFlowNotFound) {
		return http.StatusNotFound // or refresh flow list
	}
	return fmt.Errorf("get flow: %w", err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

var exists bool
err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM flows WHERE id=$1)", flowID).Scan(&exists)
// only call GetFlow/StopFlow/etc. when exists == true

Type guard

func IsFlowNotFound(err error) bool {
	return errors.Is(err, controller.ErrFlowNotFound)
}

Try / catch

flow, err := fc.GetFlow(ctx, flowID)
if err != nil {
	if IsFlowNotFound(err) {
		return ErrHTTP404 // refresh client flow list
	}
	return fmt.Errorf("get flow: %w", err)
}

Prevention

When it happens

Trigger: Any FlowController operation (GetFlow/StopFlow/FinishFlow/RenameFlow/DeleteFlow/PatchAssistant) called with a flowID that is not registered in the controller's flow map — e.g., after process restart before LoadFlows, or for an ID that never existed or was deleted.

Common situations: Client caches a flowID after server restart (workers not yet loaded); UI uses a stale ID after another user deleted the flow; race between DeleteFlow and a subsequent call; querying before startup LoadFlows completes.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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