go-task/task · error · TaskfileInvalidError

loop var must be a delimiter-separated string, list or a map

Error message

loop var must be a delimiter-separated string, list or a map

What it means

Raised by itemsFromFor (called via compiledTask) when a `for` loop's `var` does not expand to one of the supported iterable shapes: a delimiter-separated string (e.g. comma-separated with `split:`), a list (Go/JSON-style array), or a map. When the resolved variable's underlying value is any other type, the executor returns this TaskfileInvalidError wrapping the message, tagged with the Taskfile location.

Source

Thrown at variables.go:458

						values = asAnySlice(strings.Split(value, f.Split))
					} else {
						values = asAnySlice(strings.Fields(value))
					}
				case []string:
					values = asAnySlice(value)
				case []int:
					values = asAnySlice(value)
				case []any:
					values = value
				case map[string]any:
					for k, v := range value {
						keys = append(keys, k)
						values = append(values, v)
					}
				default:
					return nil, nil, errors.TaskfileInvalidError{
						URI: location.Taskfile,
						Err: errors.New("loop var must be a delimiter-separated string, list or a map"),
					}
				}
			}
		}
	}
	return values, keys, nil
}

// resolveMatrixRefs resolves any `ref:` rows in matrix and returns a new
// Matrix with those rows populated. It must not mutate the matrix passed in:
// that matrix is part of the shared, cached Task AST, and concurrent
// invocations of the same task (e.g. via parallel deps) call this with the
// same *ast.Matrix and would otherwise race on the row.Value assignment
// below, intermittently leaking a value resolved for one caller into another
// caller's expansion. See #2890.
func resolveMatrixRefs(matrix *ast.Matrix, cache *templater.Cache) (*ast.Matrix, error) {
	if matrix.Len() == 0 {
		return matrix, nil

View on GitHub (pinned to 385e5ad92a)

Solutions

  1. Check what the variable actually resolves to (print it in a task cmd) and ensure it is a list or map
  2. If looping a plain string, add `split:` under the `for` block, e.g. `for: {var: ITEMS, split: ','}`
  3. Fix the variable definition so it is a YAML list/map, or the loop's var name typo
  4. Verify YAML quoting so list values are not collapsed into one scalar string

Example fix

# before
vars:
  SERVICES: api web worker
tasks:
  build:
    cmds:
      - for: { var: SERVICES }
        task: build-one
# after
tasks:
  build:
    cmds:
      - for: { var: SERVICES, split: ' ' }
        task: build-one
Defensive patterns

Strategy: validation

Validate before calling

# Ensure loop variables are lists/maps or configure split before running task:
vars:
  SERVICES: [api, web, worker]
cmds:
  - for: { var: SERVICES }
    task: build-one

Try / catch

if err := e.Run(ctx, task, call); err != nil {
    var terr *errors.TaskfileInvalidError
    if errors.As(err, &terr) && strings.Contains(err.Error(), "loop var must be") {
        return fmt.Errorf("fix `for: var:` in %s: %w", terr.URI, err)
    }
    return err
}

Prevention

When it happens

Trigger: A task uses `for: {var: NAME, ...}` where NAME resolves to a scalar/bool/other non-iterable value — e.g. the variable was set to a plain string without `split:` configured, to a number, to a command substitution result used directly without split, or the var name is misspelled so it resolves to an unexpected value.

Common situations: Typo in the variable name inside `for: var:`; forgetting `split: ','` when looping over a plain string; looping over a variable produced by a command that returns a single value instead of a list; YAML that quotes a list into a single string.

Related errors


AI-assisted analysis of go-task/task@385e5ad92a (2026-09-05). Data as JSON: /api/errors/0efa56c8c535eb9f. Report an issue: GitHub.