{"record":{"id":"0343ecedcb8ce651","repo":"vxcontrol/pentagi","slug":"subtask-is-not-waiting-run-first","errorCode":null,"errorMessage":"subtask is not waiting, run first","messagePattern":"subtask is not waiting, run first","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/controller/subtask.go","lineNumber":266,"sourceCode":"func (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,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to put input for subtask %d: %w\", stw.subtaskCtx.SubtaskID, err)\n\t}","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/controller/subtask.go#L248-L284","documentation":"PutInput is only legal while the subtask is in the Waiting state (it was started and the agent chain returned PerformResultWaiting). If the worker has not been run yet (waiting flag false), PutInput rejects the call with 'subtask is not waiting, run first'.","triggerScenarios":"Calling PutInput before the first Run(ctx) call on a freshly created subtaskWorker; calling PutInput after Run set status to Running (waiting=false) — i.e. trying to feed a second input while the chain is executing.","commonSituations":"Race between a UI websocket handler delivering user input and the background goroutine starting Run; client retry logic double-submitting input right after the first PutInput flipped waiting=false.","solutions":["Call Run(ctx) first to start the subtask, and only feed input when Run returned and the subtask transitioned to Waiting.","Guard with IsWaiting() before calling PutInput and poll or subscribe to status instead of blindly sending.","Serialize access: ensure only one goroutine drives the run/input loop per subtask (the worker holds mx for state flips, but the caller must not race)."],"exampleFix":"// before\nif err := worker.PutInput(ctx, input); err != nil {\n\treturn err\n}\n// after\nif !worker.IsWaiting() {\n\tif err := worker.Run(ctx); err != nil {\n\t\treturn err\n\t}\n\tif !worker.IsWaiting() {\n\t\treturn fmt.Errorf(\"subtask still running; input not accepted yet\")\n\t}\n}\nreturn worker.PutInput(ctx, input)","handlingStrategy":"validation","validationCode":"// ensure the run/input sequence before sending input\nif !worker.IsWaiting() {\n\tif err := worker.Run(ctx); err != nil {\n\t\treturn err\n\t}\n}\nif !worker.IsWaiting() {\n\treturn fmt.Errorf(\"chain still executing; wait for Waiting status\")\n}","typeGuard":null,"tryCatchPattern":"if err := worker.PutInput(ctx, input); err != nil {\n\tif strings.Contains(err.Error(), \"subtask is not waiting\") {\n\t\t// wrong sequence: surface ordering guidance to the caller\n\t\treturn ErrRunFirst\n\t}\n\treturn err\n}","preventionTips":["Enforce the lifecycle: Run → (Waiting) → PutInput → Run …","Single-owner goroutine per subtaskWorker to avoid concurrent Run/PutInput races.","Subscribe to status changes instead of polling with blind calls.","After a successful PutInput, do not immediately call PutInput again — waiting is false until the chain returns."],"tags":["go","state-machine","lifecycle","race-condition"],"backgroundTag":"invalid-subtask-state-transition","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}