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.

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.

Appendix: 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)