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
- Inspect the wrapped pgx/pq error: for undefined-table or migration errors run the goose migrations.
- If sql.ErrNoRows, the subtask was deleted (e.g. by flow replacement) — abandon the stale worker rather than retrying.
- Retry the update with a fresh context (context.Background()+timeout) if the original ctx was cancelled before the write.
- 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
- Truncate oversized results before persisting.
- Retry transient DB errors with exponential backoff.
- Verify migrations are applied so the subtasks table/columns exist.
- Treat sql.ErrNoRows as 'worker is stale' and abandon rather than retry.
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
- failed to patch subtasks for task %d: %w
- token not found in database
- failed to create flow in DB: %w
- failed to get user %d: %w
- failed to get flow primary container: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/4619678acf73d287.
Report an issue: GitHub.