vxcontrol/pentagi · warning

operation %d: subtask with id %d not found for removal

Error message

operation %d: subtask with id %d not found for removal

What it means

applySubtaskOperations checks each remove operation's ID against idToIdx, the index of currently known subtasks. If the referenced subtask ID does not exist in the flow's subtask list, the patch is rejected. This catches stale or hallucinated IDs from the LLM before any mutation happens.

Source

Thrown at backend/pkg/providers/subtask_patch.go:64

	// First pass: process removals and modifications in-place
	for i, op := range patch.Operations {
		opLogger := logger.WithFields(logrus.Fields{
			"operation_index": i,
			"operation":       op.Op,
			"id":              op.ID,
			"after_id":        op.AfterID,
		})

		switch op.Op {
		case tools.SubtaskOpRemove:
			if op.ID == nil {
				err := fmt.Errorf("operation %d: remove operation missing required id field", i)
				opLogger.Error(err.Error())
				return nil, err
			}
			if _, ok := idToIdx[*op.ID]; !ok {
				err := fmt.Errorf("operation %d: subtask with id %d not found for removal", i, *op.ID)
				opLogger.Error(err.Error())
				return nil, err
			}
			removed[*op.ID] = true
			opLogger.WithField("subtask_id", *op.ID).Debug("marked subtask for removal")

		case tools.SubtaskOpModify:
			if op.ID == nil {
				err := fmt.Errorf("operation %d: modify operation missing required id field", i)
				opLogger.Error(err.Error())
				return nil, err
			}
			if op.Title == "" && op.Description == "" {
				err := fmt.Errorf("operation %d: modify operation missing both title and description fields", i)
				opLogger.Error(err.Error())
				return nil, err
			}
			idx, ok := idToIdx[*op.ID]

View on GitHub (pinned to ea665308ba)

Solutions

  1. Retry the refiner call with the current, accurate subtask list in the prompt.
  2. Verify the prompt includes the up-to-date subtask IDs the model may reference.
  3. Tolerate-and-skip unknown removals upstream if partial application is acceptable for your use case.
  4. Use a stronger model that grounds IDs in the provided list.

Example fix

// before
{"op":"remove","id":99} // no subtask 99
// after: pick an existing id from the current list
{"op":"remove","id":2}
Defensive patterns

Strategy: validation

Validate before calling

for i, op := range ops {
    if op.Op == tools.SubtaskOpRemove {
        if _, ok := idToIdx[*op.ID]; !ok {
            return fmt.Errorf("op %d: id %d not in current subtasks", i, *op.ID)
        }
    }
}

Type guard

func subtaskExists(id int64, idToIdx map[int64]int) bool {
    _, ok := idToIdx[id]
    return ok
}

Try / catch

patched, err := applySubtaskOperations(ctx, logger, ops, subtasks)
if err != nil && strings.Contains(err.Error(), "not found for removal") {
    // regenerate patch with the current subtask list
    return regeneratePatch(ctx, subtasks)
}

Prevention

When it happens

Trigger: LLM emits {"op":"remove","id":N} where N is not among the current subtask IDs — fabricated ID, ID from a previous state, or duplicate removal after the list changed mid-patch.

Common situations: Model referencing subtask IDs from an outdated snapshot; hallucinated sequential IDs; concurrent modification of the subtask list between generation and application.

Related errors


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