hashicorp/terraform · error · LockError

state locked

Error message

state locked

What it means

A NestingSet block may not contain attributes whose type resolves to cty.DynamicPseudoType. The cty set implementation hashes elements by their concrete type, so it cannot store elements of unknown/mixed type. The validator computes the block's implied type and rejects it if any leaf is dynamic.

Solutions

  1. Replace cty.DynamicPseudoType with a concrete type (e.g. cty.String for JSON-in-string, or a defined object type).
  2. Switch the block from NestingSet to NestingList, which has no such constraint.
  3. Wrap dynamic payloads in a string-typed attribute holding serialized JSON.

Example fix

// before
"rules": {
  Nesting: configschema.NestingSet,
  Block: configschema.Block{Attributes: map[string]*configschema.Attribute{
    "data": { Type: cty.DynamicPseudoType, Optional: true },
  }},
},
// after
"rules": {
  Nesting: configschema.NestingSet,
  Block: configschema.Block{Attributes: map[string]*configschema.Attribute{
    "data": { Type: cty.String, Optional: true }, // store JSON as string
  }},
},
Defensive patterns

Strategy: validation

Validate before calling

// Reject NestingSet blocks whose implied type has dynamic leaves.
func setHasDynamic(nb *configschema.NestedBlock) bool {
    if nb.Nesting != configschema.NestingSet { return false }
    return nb.Block.ImpliedType().HasDynamicTypes()
}

Type guard

// noDynamicTypes returns true when the block's implied type is free of cty.DynamicPseudoType.
func noDynamicTypes(b *configschema.Block) bool {
    return !b.ImpliedType().HasDynamicTypes()
}

Prevention

When it happens

Trigger: NestingSet block containing an attribute of type cty.DynamicPseudoType (directly or nested). Guard at internal_validate.go:94 is `ety := blockS.Block.ImpliedType(); ety.HasDynamicTypes()`.

Common situations: Using a free-form JSON attribute (cty.DynamicPseudoType) inside a set-valued block; migrating a NestingList with dynamic attrs to NestingSet.

Related errors


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

Appendix: source

Thrown at internal/backend/remote-state/inmem/backend.go:186

}

// Global level locks for inmem backends.
type lockMap struct {
	sync.Mutex
	m map[string]*statemgr.LockInfo
}

func (l *lockMap) lock(name string, info *statemgr.LockInfo) (string, error) {
	l.Lock()
	defer l.Unlock()

	lockInfo := l.m[name]
	if lockInfo != nil {
		lockErr := &statemgr.LockError{
			Info: lockInfo,
		}

		lockErr.Err = errors.New("state locked")
		// make a copy of the lock info to avoid any testing shenanigans
		*lockErr.Info = *lockInfo
		return "", lockErr
	}

	info.Created = time.Now().UTC()
	l.m[name] = info

	return info.ID, nil
}

func (l *lockMap) unlock(name, id string) error {
	l.Lock()
	defer l.Unlock()

	lockInfo := l.m[name]

	if lockInfo == nil {

View on GitHub (pinned to d32a084675)