vxcontrol/pentagi · warning

no subtasks found for task %d: %w

Error message

no subtasks found for task %d: %w

What it means

Returned when GetTaskSubtasks succeeds but returns zero rows for the task, wrapped together with the sentinel ErrNothingToLoad. It signals there are no subtasks to restore into workers — the task has no plan generated yet (or its subtasks were never created).

Source

Thrown at backend/pkg/controller/subtasks.go:52

func NewSubtaskController(taskCtx *TaskContext) SubtaskController {
	return &subtaskController{
		mx:       &sync.Mutex{},
		taskCtx:  taskCtx,
		subtasks: make(map[int64]SubtaskWorker),
	}
}

func (stc *subtaskController) LoadSubtasks(ctx context.Context, taskID int64, updater TaskUpdater) error {
	stc.mx.Lock()
	defer stc.mx.Unlock()

	subtasks, err := stc.taskCtx.DB.GetTaskSubtasks(ctx, taskID)
	if err != nil {
		return fmt.Errorf("failed to get subtasks for task %d: %w", taskID, err)
	}

	if len(subtasks) == 0 {
		return fmt.Errorf("no subtasks found for task %d: %w", taskID, ErrNothingToLoad)
	}

	for _, subtask := range subtasks {
		st, err := LoadSubtaskWorker(ctx, subtask, stc.taskCtx, updater)
		if err != nil {
			if errors.Is(err, ErrNothingToLoad) {
				continue
			}

			return fmt.Errorf("failed to create subtask worker: %w", err)
		}

		stc.subtasks[subtask.ID] = st
	}

	return nil
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check errors.Is(err, ErrNothingToLoad) at the call site and, if so, call GenerateSubtasks instead of treating it as a hard failure.
  2. Verify the task actually has subtasks in the DB (SELECT * FROM subtasks WHERE task_id = ...) before resuming.
  3. If subtasks were deleted unintentionally, re-generate them via the flow's generate-plan path.

Example fix

// before
if err := stc.LoadSubtasks(ctx, taskID, updater); err != nil {
    return err
}
// after
if err := stc.LoadSubtasks(ctx, taskID, updater); err != nil {
    if errors.Is(err, ErrNothingToLoad) {
        return stc.GenerateSubtasks(ctx)
    }
    return err
}
Defensive patterns

Strategy: fallback

Validate before calling

n, err := db.GetTaskSubtaskCount(ctx, taskID)
if err == nil && n == 0 {
    // nothing to load — generate instead
}

Type guard

func isNothingToLoad(err error) bool {
    return errors.Is(err, controller.ErrNothingToLoad)
}

Try / catch

if err := stc.LoadSubtasks(ctx, taskID, updater); err != nil {
    if isNothingToLoad(err) {
        return stc.GenerateSubtasks(ctx) // fallback: create a plan
    }
    return err
}

Prevention

When it happens

Trigger: subtaskController.LoadSubtasks is called for a taskID whose subtasks table rows are empty — e.g. resuming a flow before GenerateSubtasks ever ran, or after all subtasks were deleted.

Common situations: Server restart while a task was still in 'created' state with no generated plan; calling the resume/load API on a freshly created flow; a prior GenerateSubtasks failure left the task with no subtasks.

Related errors


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