vxcontrol/pentagi · error
subtask has already completed
Error message
subtask has already completed
What it means
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.
Source
Thrown at backend/pkg/controller/subtask.go:262
return subtask.Result, nil
}
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,View on GitHub (pinned to ea665308ba)
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).
Example fix
// before
if err := worker.PutInput(ctx, answer); err != nil {
return err
}
// after
if worker.IsCompleted() {
return fmt.Errorf("subtask already finished; create a new subtask to continue")
}
if err := worker.PutInput(ctx, answer); err != nil {
return err
} Defensive patterns
Strategy: validation
Validate before calling
// guard before calling the API
if worker.IsCompleted() {
return fmt.Errorf("subtask finished; cannot accept input")
}
if !worker.IsWaiting() {
return fmt.Errorf("subtask not waiting for input")
} Try / catch
if err := worker.PutInput(ctx, input); err != nil {
if strings.Contains(err.Error(), "subtask has already completed") {
return ErrSubtaskClosed // map to a domain error / user-facing message
}
return err
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- subtask is not waiting, run first
- 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/f318c8293823d80c.
Report an issue: GitHub.