vxcontrol/pentagi · error

failed to set subtask %d result: %w

Error message

failed to set subtask %d result: %w

What it means

SetResult persists the subtask's final result text via UpdateSubtaskResult. When that SQL update fails, the error is wrapped as 'failed to set subtask %d result'. The subtask keeps its previous (usually empty) result, so downstream consumers reading the result get nothing.

Source

Thrown at backend/pkg/controller/subtask.go:254

	return nil
}

func (stw *subtaskWorker) GetResult(ctx context.Context) (string, error) {
	subtask, err := stw.subtaskCtx.DB.GetSubtask(ctx, stw.subtaskCtx.SubtaskID)
	if err != nil {
		return "", err
	}

	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)
	}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Inspect the wrapped pgx/pq error: for undefined-table or migration errors run the goose migrations.
  2. If sql.ErrNoRows, the subtask was deleted (e.g. by flow replacement) — abandon the stale worker rather than retrying.
  3. Retry the update with a fresh context (context.Background()+timeout) if the original ctx was cancelled before the write.
  4. Truncate oversized result strings before calling SetResult to avoid column-limit rejections.

Example fix

// before
if err := stw.SetResult(ctx, bigResult); err != nil {
	return err
}
// after
result := bigResult
if len(result) > 1<<20 {
	result = result[:1<<20]
}
if err := stw.SetResult(ctx, result); err != nil {
	if errors.Is(err, context.Canceled) {
		resetCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		return stw.SetResult(resetCtx, result)
	}
	return err
}
Defensive patterns

Strategy: retry

Validate before calling

// ensure the subtask row still exists before writing the result
var exists bool
err := db.QueryRowContext(ctx, `SELECT EXISTS(SELECT 1 FROM subtasks WHERE id = $1)`, subtaskID).Scan(&exists)
if err != nil || !exists {
	// stale worker: skip result write
}

Type guard

func isRetryableDBErr(err error) bool {
	return isContextErr(err) || errors.Is(err, pgx.ErrTxClosed)
}

Try / catch

if err := worker.SetResult(ctx, result); err != nil {
	if isRetryableDBErr(err) {
		resetCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		return worker.SetResult(resetCtx, result)
	}
	return err
}

Prevention

When it happens

Trigger: Calling SetResult(ctx, result) when the DB UPDATE fails: connection drop, cancelled ctx, the subtask row was deleted (no rows error is not special-cased here), or a malformed/large result text rejected by the column type.

Common situations: Flow or task deleted concurrently while the worker tries to write its result; database connection pool exhausted under many parallel subtasks; result string exceeding column limits.

Related errors


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