Terraform 0.12.16 fixes replacement dependency cycles
The replacement graph now removes conflicting ordering edges for create_before_destroy and stops accumulating obsolete dependencies during refresh.
Replacing infrastructure can require two versions of an object to exist during the same apply. Terraform's create_before_destroy setting asks for that order, but its dependency graph also contains rules about when dependent resources can update. Terraform 0.12.16, released on November 18, fixes a case where those rules contradicted one another and formed a cycle.
A cycle diagnostic does not always mean the author wrote circular references. Terraform transforms configuration dependencies into an execution graph that includes creation and destruction separately. The defect corrected here arose during that transformation. Removing a legitimate configuration dependency would address the wrong level of the problem.
I value Terraform's dependency model because a reusable module should describe relationships without spelling out every operation in order. Replacement is a demanding test of that model. The graph must express the lifecycle requested by the configuration without introducing an impossible ordering requirement of its own.
Replacement changes the ordering problem
The resource documentation at this tag explains the default replacement behavior. When a remote API cannot update a changed argument in place, Terraform ordinarily destroys the old object and creates its replacement. The lifecycle setting reverses that order for the resource.
The relevant fragment is small and belongs inside a resource declaration.
lifecycle {
create_before_destroy = true
}
The setting is opt-in because the remote system must permit old and new objects to coexist. A unique-name constraint can make that impossible unless the configuration provides distinct names. Terraform cannot infer and activate a provider's naming workaround merely from the lifecycle setting.
Dependencies add another consideration. A dependent resource may need to wait until its dependency's replacement sequence has finished before it can update. The lifecycle graph therefore has more edges than a direct transcription of attribute references. Some express ordering requirements implied by how providers perform updates.
An extra edge conflicts with the reversed order
James Bardin describes the failure in pull request 23399. Terraform connects creator nodes directly to dependency destroy nodes so updates occur after the dependency is destroyed. The pull request explains that many resources cannot update correctly until their dependencies have completed replacement, even when configuration evaluation alone does not require the extra ordering.
That rule fails when the creator itself uses create-before-destroy. Reversing its own creation and destruction order can close a loop through the dependency's destroy node. Each individual edge has an explanation, but their combination admits no valid sequence of execution.
This is why the fix works on the transformed graph. It removes certain incoming creator edges at a create-before-destroy destroy node while reversing the resource's own destruction edge. The pull request notes that dependencies of a create-before-destroy resource also acquire that behavior, which permits the correction to identify the relevant conflict.
The implementation cannot make the complete decision when it first adds those edges because the required lifecycle information is not yet readily available at that stage. It adds the ordering conservatively and prunes the conflicting edges during the later lifecycle transformation. The author describes avoiding the unnecessary connections earlier as a possible refinement, not as something this patch already implements.
That distinction matters when interpreting the result. The patch does not abolish dependency ordering or allow arbitrary updates to run whenever possible. It corrects a class of edges that conflicts with the requested replacement order.
Stored dependencies can also become stale
The same pull request changes how refresh updates dependencies in state. Previously, appending newly discovered dependencies could leave earlier ones in place. A configuration change could then combine current relationships with obsolete relationships and produce another cycle.
Keeping the largest historical set sounds conservative, but it is not always safe. Relationships describe the ordering required by a particular configuration and object state. An old relationship that contradicts the current one can make a valid transition impossible to schedule.
The fix stops appending to the existing dependency set during that refresh operation. This is a separate correction from pruning lifecycle edges, although both concern graph information that becomes harmful when retained without regard to its current meaning. One corrects an execution transformation, while the other corrects the dependency information available to it.
The regression test includes a saved plan boundary
The patch begins with a failing regression test and a small chain of test resources. One resource uses create-before-destroy, and the others supply dependencies involved in replacement. The test starts with state containing prior objects and their recorded dependencies, rather than testing only a fresh creation.
It also serializes and deserializes the plan before applying it through a new context. The test comment explicitly connects this to running a saved plan in a later command. That exercises a boundary where graph reconstruction must preserve the information needed for replacement without relying on the original planning context's memory.
I find that test shape appropriate for the defect. A fresh apply would not exercise replacement of existing objects, and keeping only one in-memory context would omit an important execution path. The source supplies evidence for a specific corrected scenario, without establishing that every possible lifecycle cycle has disappeared.
The upgrade command also stops panicking on range errors
Another fix in this release handles out-of-range numeric values in 0.12upgrade. Pull request 23394 explains that syntactically valid input could still make integer or floating-point conversion panic. It moves the conversion logic into configuration upgrading so an error can reach the user.
That change does not make the invalid numeric value acceptable. It improves failure reporting while preserving the existing upgrade command's validation scope. Alongside the lifecycle correction, it makes this patch release about more accurate execution and diagnostics at boundaries where syntactic validity alone does not establish that an operation can proceed.