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
- Make every element of the tags list/set a string, e.g. `["a", "b"]`.
- 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
- Keep tags lists homogeneous strings.
- Type tag variables as set(string)/list(string).
- Avoid list(any) for tags.
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
- tag object values must be strings
- your version of Terraform Enterprise does not support…
- at most 1 action can be invoked per operation
- can't set both encryption_key and kms_encryption_key
- Cannot set both 'source' and 'content'
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)