{"record":{"id":"6d40e2e213745e4c","repo":"vxcontrol/pentagi","slug":"failed-to-finish-flow-d-w","errorCode":null,"errorMessage":"failed to finish flow %d: %w","messagePattern":"failed to finish flow (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/flows.go","lineNumber":386,"sourceCode":"\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}\n\n\tdelete(fc.flows, flowID)\n\n\treturn nil\n}\n\nfunc (fc *flowController) RenameFlow(ctx context.Context, flowID int64, title string) 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\treturn flow.Rename(ctx, title)\n}","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/flows.go#L368-L404","documentation":"FinishFlow looks up the in-memory flow worker for flowID and calls its Finish(ctx) method to terminate the flow; when that worker-level finish fails, the error is wrapped as \"failed to finish flow %d: %w\" and returned. This wrapping adds the flow ID context so the caller can identify which flow could not be shut down. The controller deliberately does not delete the flow from its map until Finish succeeds, so a failed finish leaves the flow worker loaded and retryable.","triggerScenarios":"Calling FlowController.FinishFlow(ctx, flowID) when the underlying flowWorker.Finish(ctx) returns an error — e.g. its database writes (status transition, worker record update) fail due to a DB connection problem, constraint violation, or the context is cancelled/deadlines out mid-finish.","commonSituations":"Database down or restarted while a flow is being finished; request context cancelled because the HTTP/GraphQL client disconnected before the finish transaction committed; serialized DB access contention when many flows finish simultaneously; calling FinishFlow after the DB row was already modified externally.","solutions":["Inspect the wrapped cause (errors.Unwrap / %w chain or server logs) to see the underlying DB or context error.","Verify PostgreSQL connectivity and that the database migrations are up to date.","Retry FinishFlow: it is safe because the flow is only removed from the map after a successful Finish.","Check that the caller's context is not cancelled or too short for the finish work (e.g. don't finish flows with an already-expired request context)."],"exampleFix":"// before: context dies with the HTTP request, finish may abort\nerr := flows.FinishFlow(r.Context(), flowID)\n\n// after: use a detached context with a sane timeout for teardown\nctx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), 30*time.Second)\ndefer cancel()\nerr := flows.FinishFlow(ctx, flowID)","handlingStrategy":"retry","validationCode":"// ensure the flow is still loaded before finishing\nif _, err := flows.GetFlow(ctx, flowID); err != nil {\n    return err // ErrFlowNotFound — nothing to finish\n}","typeGuard":"null","tryCatchPattern":"if err := flows.FinishFlow(ctx, flowID); err != nil {\n    var ctxErr error\n    if errors.As(err, &ctxErr) && (errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)) {\n        // retry with a detached, time-bounded context\n        ctx2, cancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second)\n        defer cancel()\n        err = flows.FinishFlow(ctx2, flowID)\n    }\n    return err\n}","preventionTips":["Always finish flows with a context that outlives the HTTP request (context.WithoutCancel + timeout).","Monitor PostgreSQL health; finish operations fail with the DB.","Treat this error as retryable — the flow is only removed from the map after success.","Log the unwrapped cause to distinguish DB errors from cancellation."],"tags":["database","go","flow-lifecycle","wrapped-error"],"backgroundTag":"flow-finish-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}