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

  1. Call Run(ctx) first to start the subtask, and only feed input when Run returned and the subtask transitioned to Waiting.
  2. Guard with IsWaiting() before calling PutInput and poll or subscribe to status instead of blindly sending.
  3. 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

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


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