{"record":{"id":"dad2d5d7a15ffe57","repo":"vxcontrol/pentagi","slug":"flow-already-stopped","errorCode":null,"errorMessage":"flow already stopped","messagePattern":"flow already stopped","errorType":"exception","errorClass":"ErrFlowAlreadyStopped","httpStatus":null,"severity":"info","filePath":"backend/pkg/controller/flows.go","lineNumber":24,"sourceCode":"\t\"fmt\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"pentagi/pkg/config\"\n\t\"pentagi/pkg/database\"\n\t\"pentagi/pkg/docker\"\n\t\"pentagi/pkg/graph/subscriptions\"\n\t\"pentagi/pkg/providers\"\n\t\"pentagi/pkg/providers/provider\"\n\t\"pentagi/pkg/tools\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tErrFlowNotFound       = fmt.Errorf(\"flow not found\")\n\tErrFlowAlreadyStopped = fmt.Errorf(\"flow already stopped\")\n)\n\ntype FlowController interface {\n\tCreateFlow(\n\t\tctx context.Context,\n\t\tuserID int64,\n\t\tinput string,\n\t\tprvname provider.ProviderName,\n\t\tprvtype provider.ProviderType,\n\t\tfunctions *tools.Functions,\n\t\tresources []database.UserResource,\n\t) (FlowWorker, error)\n\tCreateAssistant(\n\t\tctx context.Context,\n\t\tuserID int64,\n\t\tflowID int64,\n\t\tinput string,\n\t\tuseAgents bool,","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/flows.go#L6-L42","documentation":"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.","triggerScenarios":"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.","commonSituations":"Duplicate stop requests from the UI; retry logic re-issuing a stop that actually succeeded; two operators stopping the same flow concurrently.","solutions":["Treat it as success/idempotent: the flow is stopped either way — check errors.Is(err, controller.ErrFlowAlreadyStopped) and continue.","Disable the stop action in the UI once flow status is finished/stopped.","Serialize stop operations or use per-flow locking if concurrent stops are common.","Verify current flow status via GetFlow before stopping."],"exampleFix":"// before\nif err := fc.StopFlow(ctx, flowID); err != nil {\n\treturn fmt.Errorf(\"stop flow: %w\", err)\n}\n// after\nif err := fc.StopFlow(ctx, flowID); err != nil && !errors.Is(err, controller.ErrFlowAlreadyStopped) {\n\treturn fmt.Errorf(\"stop flow: %w\", err)\n}","handlingStrategy":"type-guard","validationCode":"var status string\nerr := db.QueryRow(\"SELECT status FROM flows WHERE id=$1\", flowID).Scan(&status)\n// skip StopFlow if status is already finished/stopped","typeGuard":"func IsFlowAlreadyStopped(err error) bool {\n\treturn errors.Is(err, controller.ErrFlowAlreadyStopped)\n}","tryCatchPattern":"err := fc.StopFlow(ctx, flowID)\nif err != nil && !IsFlowAlreadyStopped(err) {\n\treturn fmt.Errorf(\"stop flow: %w\", err)\n}\n// ErrFlowAlreadyStopped is treated as idempotent success","preventionTips":["Treat this error as success — the desired end state is already reached.","Disable stop buttons once flow status shows stopped/finished.","Deduplicate stop requests (per-flow mutex or request ID).","Check flow status via GetFlow before issuing a stop."],"tags":["idempotency","sentinel-error","flow-lifecycle"],"backgroundTag":"already-completed-conflict","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}