{"record":{"id":"fa14e352922adefb","repo":"vxcontrol/pentagi","slug":"failed-to-set-subtask-d-status-to-finished-w","errorCode":null,"errorMessage":"failed to set subtask %d status to finished: %w","messagePattern":"failed to set subtask (.+?) status to finished: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/subtask.go","lineNumber":341,"sourceCode":"\t\t}\n\t\terrChainConsistency := stw.subtaskCtx.Provider.EnsureChainConsistency(ctx, msgChainID)\n\t\tif errChainConsistency != nil {\n\t\t\terr = errors.Join(err, errChainConsistency)\n\t\t}\n\t\t_ = stw.SetStatus(ctx, database.SubtaskStatusWaiting)\n\t\treturn fmt.Errorf(\"failed to perform agent chain for subtask %d: %w\", subtaskID, err)\n\t}\n\n\tswitch performResult {\n\tcase providers.PerformResultWaiting:\n\t\tif err := stw.SetStatus(ctx, database.SubtaskStatusWaiting); err != nil {\n\t\t\tstw.handleInterrupting(err)\n\t\t\treturn err\n\t\t}\n\tcase providers.PerformResultDone:\n\t\tif err := stw.SetStatus(ctx, database.SubtaskStatusFinished); err != nil {\n\t\t\tstw.handleInterrupting(err)\n\t\t\treturn fmt.Errorf(\"failed to set subtask %d status to finished: %w\", subtaskID, err)\n\t\t}\n\tcase providers.PerformResultError:\n\t\tif err := stw.SetStatus(ctx, database.SubtaskStatusFailed); err != nil {\n\t\t\tstw.handleInterrupting(err)\n\t\t\treturn fmt.Errorf(\"failed to set subtask %d status to failed: %w\", subtaskID, err)\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown perform result: %d\", performResult)\n\t}\n\n\treturn nil\n}\n\n// handleInterrupting sets this subtask (and task/flow via SetStatus back-propagation)\n// to Waiting when err is context.Canceled or context.DeadlineExceeded. Use after the subtask\n// was advanced past Waiting (e.g. Running) but the run aborts before PerformAgentChain's\n// normal error handler, or when a late SetStatus fails with a context interruption.\nfunc (stw *subtaskWorker) handleInterrupting(err error) {","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/subtask.go#L323-L359","documentation":"Wraps the error returned by subtaskWorker.SetStatus when the worker tried to transition a subtask to 'finished' after the agent chain reported PerformResultDone. The database status update failed (likely context cancellation, DB connectivity loss, or a status conflict), so the subtask is left in a non-finished state even though the work succeeded. handleInterrupting is invoked first, so context.Canceled/DeadlineExceeded resets the subtask to Waiting for a later retry.","triggerScenarios":"subtaskWorker.Run() gets providers.PerformResultDone from Provider.PerformAgentChain and the subsequent SetStatus(ctx, SubtaskStatusFinished) returns an error — e.g. the run context was canceled mid-update, the PostgreSQL connection dropped, or the row was concurrently modified.","commonSituations":"Flow cancellation or shutdown racing with task completion; DB pod restart/connection pool exhaustion in Kubernetes; pgx 'conn busy' or serialization failures under heavy concurrent flow updates; the parent task being deleted while its last subtask finishes.","solutions":["Check DB connectivity/pool health (pgx logs) and retry the flow; if the error is context.Canceled the worker already reset the subtask to Waiting, so re-run it.","Ensure the context passed to Run is not canceled before completion — don't abort the flow goroutine while subtasks are finishing.","Verify no concurrent code path updates the same subtask status (idempotency: finished→finished transitions or unique constraints failing).","Check the wrapped error (%w) in logs for the root cause (e.g. SQLSTATE) and address that specifically."],"exampleFix":"// before: run with the flow's context that gets canceled on timeout\nerr := stw.Run(flowCtx)\n// after: tolerate interruptions — worker resets to Waiting; re-dispatch\nif err != nil {\n    var subtaskErr *controller.SubtaskError\n    if errors.Is(err, context.Canceled) {\n        // subtask was reset to Waiting by handleInterrupting; safe to retry\n        stc.TryRequeueSubtask(subtaskID)\n    }\n}","handlingStrategy":"retry","validationCode":"if stw.IsCompleted() { return nil } // never run a terminal subtask\nif err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"db unavailable: %w\", err) }","typeGuard":"func isInterrupting(err error) bool {\n    return errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)\n}","tryCatchPattern":"if err := stw.Run(ctx); err != nil {\n    if isInterrupting(err) {\n        // worker reset subtask to Waiting; safe to requeue\n        requeue(subtaskID)\n        return nil\n    }\n    return fmt.Errorf(\"run failed: %w\", err)\n}","preventionTips":["Don't cancel the flow context while subtasks are transitioning to a terminal state.","Monitor PostgreSQL connection-pool health before dispatching long flows.","Use errors.Is for context.Canceled to distinguish retryable interruptions from hard failures.","Make status transitions idempotent at the DB layer (ON CONFLICT DO NOTHING on same-status updates)."],"tags":["database","subtask","state-transition","context-cancellation"],"backgroundTag":"subtask-status-update-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}