{"record":{"id":"b74aa312299cde5b","repo":"vxcontrol/pentagi","slug":"failed-to-stop-flow-d-w","errorCode":null,"errorMessage":"failed to stop flow %d: %w","messagePattern":"failed to stop flow (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/flows.go","lineNumber":369,"sourceCode":"\tif !ok {\n\t\treturn nil, ErrFlowNotFound\n\t}\n\n\treturn flow, nil\n}\n\nfunc (fc *flowController) StopFlow(ctx context.Context, flowID int64) error {\n\tfc.mx.Lock()\n\tdefer fc.mx.Unlock()\n\n\tflow, ok := fc.flows[flowID]\n\tif !ok {\n\t\treturn ErrFlowNotFound\n\t}\n\n\terr := flow.Stop(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to stop flow %d: %w\", flowID, err)\n\t}\n\n\treturn nil\n}\n\nfunc (fc *flowController) FinishFlow(ctx context.Context, flowID int64) error {\n\tfc.mx.Lock()\n\tdefer fc.mx.Unlock()\n\n\tflow, ok := fc.flows[flowID]\n\tif !ok {\n\t\treturn ErrFlowNotFound\n\t}\n\n\terr := flow.Finish(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to finish flow %d: %w\", flowID, err)\n\t}","sourceCodeStart":351,"sourceCodeEnd":387,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/flows.go#L351-L387","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nctl.StopFlow(2*time.Second ctx) // canceled mid-teardown -> docker remove fails\n// after\nctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\ndefer cancel()\nerr := ctl.StopFlow(ctx, flowID)","handlingStrategy":"try-catch","validationCode":"// ensure the flow exists and docker is reachable before stopping\nif _, err := ctl.GetFlow(ctx, flowID); err != nil {\n    return err // ErrFlowNotFound\n}\nif err := dockerClient.Ping(ctx); err != nil {\n    return fmt.Errorf(\"docker daemon unavailable\")\n}","typeGuard":null,"tryCatchPattern":"if err := ctl.StopFlow(ctx, flowID); err != nil {\n    if strings.Contains(err.Error(), \"failed to stop flow\") {\n        log.Errorf(\"stop flow %d: %v\", flowID, err)\n        // check docker daemon / DB per wrapped cause, then retry\n    }\n    return err\n}","preventionTips":["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."],"tags":["go","docker","flow-lifecycle","teardown","wrapped-error"],"backgroundTag":"flow-stop-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}