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

  1. Call IsCompleted() before PutInput and surface a friendly 'subtask already finished' message to the user instead of an error.
  2. 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.
  3. 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

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


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/f318c8293823d80c. Report an issue: GitHub.