hashicorp/terraform · error

%s%s: name defined as both attribute and child block type

Error message

%s%s: name defined as both attribute and child block type

What it means

The same name is present in both b.Attributes and b.BlockTypes (checked at lines 55-56) - Terraform cannot decide whether that identifier is an attribute or a nested block, so it is rejected. Note this check runs before the name-format check (line 57) and is mutually exclusive with it via else-if.

Source

Thrown at internal/configs/configschema/internal_validate.go:56

			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
			continue
		}
		multiErr = errors.Join(multiErr, attrS.internalValidate(name, prefix))

		// all attributes within a computed block must also be computed
		if b.Computed && !attrS.Computed {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: all attributes within computed blocks must also be computed", prefix, name))
		}
	}

	for name, blockS := range b.BlockTypes {
		if blockS == nil {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: block schema is nil", prefix, name))
			continue
		}

		if _, isAttr := b.Attributes[name]; isAttr {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name))
		} else if !validName.MatchString(name) {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
		}
		if !blockS.Deprecated && blockS.DeprecationMessage != "" {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: DeprecationMessage must not be set when Deprecated is false", prefix, name))
		}

		if blockS.MinItems < 0 || blockS.MaxItems < 0 {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name))
		}

		// any nested blocks within a computed block must also be computed
		if b.Computed && !blockS.Computed {
			multiErr = errors.Join(multiErr, fmt.Errorf("%s%s: all nested blocks within computed blocks must also be computed", prefix, name))
		}

		switch blockS.Nesting {
		case NestingSingle:

View on GitHub (pinned to c9def3e214)

Solutions

  1. Rename one of them so attribute and block-type names are disjoint.
  2. Remove the obsolete entry (e.g. delete the attribute when it became a block).

Example fix

// before
Attributes: map[string]*configschema.Attribute{ "rule": {Type: cty.String, Optional: true} }
BlockTypes:  map[string]*configschema.NestedBlock{ "rule": {Nesting: configschema.NestingList} }

// after
Attributes: map[string]*configschema.Attribute{ "rule": {Type: cty.String, Optional: true} }
BlockTypes:  map[string]*configschema.NestedBlock{ "rule_block": {Nesting: configschema.NestingList} }
Defensive patterns

Strategy: validation

Validate before calling

for name := range block.BlockTypes {
    if _, dup := block.Attributes[name]; dup {
        return fmt.Errorf("name %q is both attribute and block", name)
    }
}

Prevention

When it happens

Trigger: Defining `b.Attributes["foo"]` and `b.BlockTypes["foo"]` in the same Block, usually from merging two schema fragments or a copy-paste during an attribute->block conversion.

Common situations: Schema composition/inheritance, converting an attribute to a block without removing the old entry, generated schemas merging maps.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/ca4b649001d38c1b. Report an issue: GitHub.