vxcontrol/pentagi · warning

subtask %d has no msg chains: %w

Error message

subtask %d has no msg chains: %w

What it means

LoadSubtaskWorker requires at least one primary message chain to build a subtaskWorker; when GetSubtaskPrimaryMsgChains returns zero rows it wraps ErrNothingToLoad with the subtask ID. This signals the subtask has no conversation history to resume — the chain was never created or was deleted.

Source

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

			Status: database.SubtaskStatusCreated,
			ID:     subtask.ID,
		})
		if err != nil {
			return nil, fmt.Errorf("failed to update subtask %d status to created: %w", subtask.ID, err)
		}
	case database.SubtaskStatusCreated:
		return nil, fmt.Errorf("subtask %d has created yet: %w", subtask.ID, ErrNothingToLoad)
	default:
		return nil, fmt.Errorf("unexpected subtask status: %s", subtask.Status)
	}

	msgChains, err := taskCtx.DB.GetSubtaskPrimaryMsgChains(ctx, database.Int64ToNullInt64(&subtask.ID))
	if err != nil {
		return nil, fmt.Errorf("failed to get subtask primary msg chains for subtask %d: %w", subtask.ID, err)
	}

	if len(msgChains) == 0 {
		return nil, fmt.Errorf("subtask %d has no msg chains: %w", subtask.ID, ErrNothingToLoad)
	}

	return &subtaskWorker{
		mx: &sync.RWMutex{},
		subtaskCtx: &SubtaskContext{
			MsgChainID:         msgChains[0].ID,
			SubtaskID:          subtask.ID,
			SubtaskTitle:       subtask.Title,
			SubtaskDescription: subtask.Description,
			TaskContext:        *taskCtx,
		},
		updater:   updater,
		completed: completed,
		waiting:   waiting,
	}, nil
}

func (stw *subtaskWorker) GetMsgChainID() int64 {

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check errors.Is(err, ErrNothingToLoad) and skip or re-initialize the subtask's chain instead of failing the batch.
  2. Verify chains were actually created for the subtask (query msg_chain by subtask ID).
  3. If chains were deleted intentionally, mark the subtask failed/finished rather than leaving it loadable.
  4. Fix ordering so a subtask's status only advances after its primary chain exists.

Example fix

// before
worker, err := LoadSubtaskWorker(ctx, taskCtx, subtask)
if err != nil {
    return err
}
// after
worker, err := LoadSubtaskWorker(ctx, taskCtx, subtask)
if err != nil {
    if errors.Is(err, ErrNothingToLoad) {
        _ = taskCtx.DB.UpdateSubtaskStatus(ctx, database.UpdateSubtaskStatusParams{
            Status: database.SubtaskStatusFailed, ID: subtask.ID,
        })
        return nil
    }
    return err
}
Defensive patterns

Strategy: type-guard

Validate before calling

// confirm at least one primary chain exists before loading
var n int
err := db.QueryRowContext(ctx,
    `SELECT COUNT(*) FROM msg_chain WHERE subtask_id=$1`, subtaskID).Scan(&n)
if err == nil && n == 0 {
    return fmt.Errorf("subtask %d has no chains; mark failed instead", subtaskID)
}

Type guard

func IsNoMsgChains(err error) bool {
    return errors.Is(err, controller.ErrNothingToLoad) &&
        strings.Contains(err.Error(), "no msg chains")
}

Try / catch

worker, err := LoadSubtaskWorker(ctx, taskCtx, subtask)
if err != nil {
    if errors.Is(err, controller.ErrNothingToLoad) {
        _ = db.UpdateSubtaskStatus(ctx, database.UpdateSubtaskStatusParams{
            Status: database.SubtaskStatusFailed, ID: subtask.ID})
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: A subtask reached a non-initial status but has no primary msg chain rows — e.g. chain creation failed earlier (see error 142) yet the status advanced, or chains were deleted by cleanup while the subtask row remained.

Common situations: Partial failure between chain creation and status update; manual DB cleanup removing msg_chain rows; recovery scan picking up orphaned subtasks; bug in chain-per-subtask bookkeeping.

Related errors


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