hashicorp/terraform · error
consul lock was lost
Error message
consul lock was lost
What it means
NestingGroup blocks are unconditionally a single, always-present group; the MinItems/MaxItems limits do not apply and must both be left at 0. Setting either is a schema definition error.
Solutions
- Set both MinItems: 0 and MaxItems: 0 on the NestingGroup block.
- If you need occurrence limits, use NestingList or NestingSet instead of NestingGroup.
Example fix
// before
"features": { Nesting: configschema.NestingGroup, MinItems: 1, MaxItems: 1 },
// after
"features": { Nesting: configschema.NestingGroup, MinItems: 0, MaxItems: 0 }, Defensive patterns
Strategy: validation
Validate before calling
// NestingGroup must keep MinItems and MaxItems at 0.
func validGroupCounts(nb *configschema.NestedBlock) bool {
return nb.Nesting != configschema.NestingGroup || (nb.MinItems == 0 && nb.MaxItems == 0)
} Type guard
func groupCountsZero(min, max int) bool { return min == 0 && max == 0 } Prevention
- NestingGroup is a single always-present group; counts are meaningless.
- When converting from NestingList, clear MinItems/MaxItems.
- Use a builder that sets defaults per nesting mode.
When it happens
Trigger: A NestedBlock with Nesting: NestingGroup and MinItems or MaxItems != 0. Guard at internal_validate.go:82 is `blockS.MinItems != 0 || blockS.MaxItems != 0`.
Common situations: Changing NestingList to NestingGroup without clearing the count fields; assuming group semantics still need a min count.
Related errors
- cannot delete default state
- failed to append certs
- missing state name
- workspaces not supported
- execution halted
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/19a470b199708ded.
Report an issue: GitHub.
Appendix: source
Thrown at internal/backend/remote-state/consul/client.go:40
"github.com/hashicorp/terraform/internal/states/statemgr"
"github.com/hashicorp/terraform/internal/tfdiags"
)
const (
lockSuffix = "/.lock"
lockInfoSuffix = "/.lockinfo"
// The Session TTL associated with this lock.
lockSessionTTL = "15s"
// the delay time from when a session is lost to when the
// lock is released by the server
lockDelay = 5 * time.Second
// interval between attempts to reacquire a lost lock
lockReacquireInterval = 2 * time.Second
)
var lostLockErr = errors.New("consul lock was lost")
// RemoteClient is a remote client that stores data in Consul.
type RemoteClient struct {
Client *consulapi.Client
Path string
GZip bool
mu sync.Mutex
// lockState is true if we're using locks
lockState bool
// The index of the last state we wrote.
// If this is > 0, Put will perform a CAS to ensure that the state wasn't
// changed during the operation. This is important even with locks, because
// if the client loses the lock for some reason, then reacquires it, we
// need to make sure that the state was not modified.
modifyIndex uint64
View on GitHub (pinned to d32a084675)