{"record":{"id":"f318c8293823d80c","repo":"vxcontrol/pentagi","slug":"subtask-has-already-completed","errorCode":null,"errorMessage":"subtask has already completed","messagePattern":"subtask has already completed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/subtask.go","lineNumber":262,"sourceCode":"\n\treturn subtask.Result, nil\n}\n\nfunc (stw *subtaskWorker) SetResult(ctx context.Context, result string) error {\n\t_, err := stw.subtaskCtx.DB.UpdateSubtaskResult(ctx, database.UpdateSubtaskResultParams{\n\t\tResult: result,\n\t\tID:     stw.subtaskCtx.SubtaskID,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set subtask %d result: %w\", stw.subtaskCtx.SubtaskID, err)\n\t}\n\n\treturn nil\n}\n\nfunc (stw *subtaskWorker) PutInput(ctx context.Context, input string) error {\n\tif stw.IsCompleted() {\n\t\treturn fmt.Errorf(\"subtask has already completed\")\n\t}\n\n\tif !stw.IsWaiting() {\n\t\treturn fmt.Errorf(\"subtask is not waiting, run first\")\n\t}\n\n\terr := stw.subtaskCtx.Provider.PutInputToAgentChain(ctx, stw.subtaskCtx.MsgChainID, input)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to put input for subtask %d: %w\", stw.subtaskCtx.SubtaskID, err)\n\t}\n\n\t_, err = stw.subtaskCtx.MsgLog.PutSubtaskMsg(\n\t\tctx,\n\t\tdatabase.MsglogTypeInput,\n\t\tstw.subtaskCtx.TaskID,\n\t\tstw.subtaskCtx.SubtaskID,\n\t\t\"\", // thinking is empty because this is input\n\t\tinput,","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/subtask.go#L244-L280","documentation":"PutInput refuses to accept new input once the subtask worker's completed flag is set (status Finished/Failed, or completed via SetStatus). The error is a plain state-machine guard: the message chain for this subtask is closed and no further input can be appended.","triggerScenarios":"Calling PutInput(ctx, input) on a subtaskWorker whose Run already finished (performResult Done/Error) or whose SetStatus marked it completed (e.g. subtask row disappeared and the worker self-marked complete).","commonSituations":"UI/user answering a 'waiting for input' prompt after the LLM chain already finished or failed; a race where two callers feed input concurrently and the other path completed the subtask; stale worker after flow replacement.","solutions":["Call IsCompleted() before PutInput and surface a friendly 'subtask already finished' message to the user instead of an error.","Refresh the subtask state from the DB via GetStatus and create a new subtask if more work is required — a completed subtask cannot be reopened.","Fix caller orchestration so input is only offered while the provider returned PerformResultWaiting (subtask status Waiting)."],"exampleFix":"// before\nif err := worker.PutInput(ctx, answer); err != nil {\n\treturn err\n}\n// after\nif worker.IsCompleted() {\n\treturn fmt.Errorf(\"subtask already finished; create a new subtask to continue\")\n}\nif err := worker.PutInput(ctx, answer); err != nil {\n\treturn err\n}","handlingStrategy":"validation","validationCode":"// guard before calling the API\nif worker.IsCompleted() {\n\treturn fmt.Errorf(\"subtask finished; cannot accept input\")\n}\nif !worker.IsWaiting() {\n\treturn fmt.Errorf(\"subtask not waiting for input\")\n}","typeGuard":null,"tryCatchPattern":"if err := worker.PutInput(ctx, input); err != nil {\n\tif strings.Contains(err.Error(), \"subtask has already completed\") {\n\t\treturn ErrSubtaskClosed // map to a domain error / user-facing message\n\t}\n\treturn err\n}","preventionTips":["Always check IsCompleted()/IsWaiting() before PutInput.","Only offer the input UI when the subtask status is Waiting.","Reconstruct workers from DB state after flow resume instead of reusing stale in-memory objects.","Treat a completed subtask as immutable; create a new subtask for follow-up work."],"tags":["go","state-machine","lifecycle"],"backgroundTag":"subtask-already-completed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}