hashicorp/terraform · error

tag elements must be strings

Error message

tag elements must be strings

What it means

Returned by the cloud backend config parser (cloud/backend.go:523) when `workspaces.tags` is a tuple/set type but an element is not a string. The parser iterates element-by-element building []string; if `!v.Type().Equals(cty.String)` it appends this error and returns, rejecting the backend configuration.

Solutions

  1. Make every element of the tags list/set a string, e.g. `["a", "b"]`.
  2. Type the supplying variable as set(string) / list(string).

Example fix

# before
workspaces {
  tags = ["env:prod", 9, "region:us"]
}
# after
workspaces {
  tags = ["env:prod", "region:us"]
}
Defensive patterns

Strategy: type-guard

Validate before calling

func tagElementsAllStrings(v []any) error {
    for _, e := range v {
        if _, ok := e.(string); !ok { return errors.New("tag elements must be strings") }
    }
    return nil
}

Type guard

func tagSetAllString(v cty.Value) bool {
    if !(v.Type().IsTupleType() || v.Type().IsSetType()) { return false }
    it := v.ElementIterator()
    for it.Next() {
        _, el := it.Element()
        if !el.Type().Equals(cty.String) { return false }
    }
    return true
}

Prevention

When it happens

Trigger: A cloud backend block with `workspaces { tags = ["a", 1, "b"] }` (mixed/non-string element) — the set/tuple branch at cloud/backend.go:514-524 hits a non-string element.

Common situations: Mixing numbers/bools into a list of tags. A variable typed as list(any) that yields non-string elements.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/7d910d9ef84af5e7. Report an issue: GitHub.

Appendix: source

Thrown at internal/cloud/backend.go:523

			if val.Type().IsObjectType() || val.Type().IsMapType() {
				for k, v := range val.AsValueMap() {
					if v.Type() != cty.String {
						diags = diags.Append(errors.New("tag object values must be strings"))
						return ret, diags
					}
					tagsAsMap[k] = v.AsString()
				}
				log.Printf("[TRACE] cloud: using tags %q from cloud config block", tagsAsMap)
				ret.workspaceMapping.TagsAsMap = tagsAsMap
			} else if val.Type().IsTupleType() || val.Type().IsSetType() {
				var tagsAsSet []string
				length := val.LengthInt()
				if length > 0 {
					it := val.ElementIterator()
					for it.Next() {
						_, v := it.Element()
						if !v.Type().Equals(cty.String) {
							diags = diags.Append(errors.New("tag elements must be strings"))
							return ret, diags
						}
						if vs := v.AsString(); vs != "" {
							tagsAsSet = append(tagsAsSet, vs)
						}
					}
				}

				log.Printf("[TRACE] cloud: using tags %q from cloud config block", tagsAsSet)
				ret.workspaceMapping.TagsAsSet = tagsAsSet
			} else {
				diags = diags.Append(fmt.Errorf("tags must be a set or object, not %s", val.Type().FriendlyName()))
				return ret, diags
			}
		}
		if val := workspaces.GetAttr("project"); !val.IsNull() {
			project = val.AsString()
			log.Printf("[TRACE] cloud: found project name %q in cloud config block", project)

View on GitHub (pinned to d32a084675)