vxcontrol/pentagi · error
subtask is not waiting, run first
Error message
subtask is not waiting, run first
What it means
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'.
Source
Thrown at backend/pkg/controller/subtask.go:266
func (stw *subtaskWorker) SetResult(ctx context.Context, result string) error {
_, err := stw.subtaskCtx.DB.UpdateSubtaskResult(ctx, database.UpdateSubtaskResultParams{
Result: result,
ID: stw.subtaskCtx.SubtaskID,
})
if err != nil {
return fmt.Errorf("failed to set subtask %d result: %w", stw.subtaskCtx.SubtaskID, err)
}
return nil
}
func (stw *subtaskWorker) PutInput(ctx context.Context, input string) error {
if stw.IsCompleted() {
return fmt.Errorf("subtask has already completed")
}
if !stw.IsWaiting() {
return fmt.Errorf("subtask is not waiting, run first")
}
err := stw.subtaskCtx.Provider.PutInputToAgentChain(ctx, stw.subtaskCtx.MsgChainID, input)
if err != nil {
return fmt.Errorf("failed to put input for subtask %d: %w", stw.subtaskCtx.SubtaskID, err)
}
_, err = stw.subtaskCtx.MsgLog.PutSubtaskMsg(
ctx,
database.MsglogTypeInput,
stw.subtaskCtx.TaskID,
stw.subtaskCtx.SubtaskID,
"", // thinking is empty because this is input
input,
)
if err != nil {
return fmt.Errorf("failed to put input for subtask %d: %w", stw.subtaskCtx.SubtaskID, err)
}View on GitHub (pinned to ea665308ba)
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).
Example fix
// before
if err := worker.PutInput(ctx, input); err != nil {
return err
}
// after
if !worker.IsWaiting() {
if err := worker.Run(ctx); err != nil {
return err
}
if !worker.IsWaiting() {
return fmt.Errorf("subtask still running; input not accepted yet")
}
}
return worker.PutInput(ctx, input) Defensive patterns
Strategy: validation
Validate before calling
// ensure the run/input sequence before sending input
if !worker.IsWaiting() {
if err := worker.Run(ctx); err != nil {
return err
}
}
if !worker.IsWaiting() {
return fmt.Errorf("chain still executing; wait for Waiting status")
} Try / catch
if err := worker.PutInput(ctx, input); err != nil {
if strings.Contains(err.Error(), "subtask is not waiting") {
// wrong sequence: surface ordering guidance to the caller
return ErrRunFirst
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- subtask has already completed
- subtask is waiting, put input first
- flow %d has status %s: loading aborted: %w
- failed to set flow %d status: %w
- failed to finish assistant %d: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/0343ecedcb8ce651.
Report an issue: GitHub.