Terraform v1.16.3 was published on 16 September 2026. The patch is four bug fixes. The apply failure in the notes is destroy = false on instances that also use create_before_destroy.
The full release notes and downloads are on the GitHub release page.
Destroy false beside create_before_destroy ¶
destroy = false tells Terraform to release an object from state instead of calling the provider destroy hook. create_before_destroy creates the replacement first and parks the prior object in a deposed slot until the old object is removed. A replacement, such as a change to triggers_replace on terraform_data, deposes that prior object. With both arguments set, Terraform 1.16 planned a Forget action for the deposed key and then handed the plan to NodeDestroyDeposedResourceInstanceObject. That node only destroys. Apply stopped with Unknown action Forget and Provider returned invalid result object after apply, including when the resource was the built in terraform_data type. Core scheduled Forget on a destroy node. The provider then returned a null object, which is what the error reports.
The same failure happens when create_before_destroy is not set on the resource and is only propagated from a dependent. Configurations that set only one of the two arguments already applied. The combination has failed in every 1.16 build that accepts destroy = false. A failed apply aborts, so the provider object stays in place and the state update does not finish.
#39169 adds a ForgetThenCreate action beside the existing CreateThenForget, and threads it through stack plans, the RPC API, and CLI output. NodeForgetDeposedResourceInstanceObject now reports create_before_destroy, so graph ordering stays aligned with deposed keys. The same patch makes replace_triggered_by notice forget actions. Move to v1.16.3 before the next replacement of a resource that sets both lifecycle arguments.
Function results with more than one mark ¶
Marks tag a value as sensitive, ephemeral, or deprecated. One value can carry several marks. Core compared function arguments and results with the Go GoString of the cty value. Mark order in that string is unstable, so the same inputs could compare equal on one run and differ on the next.
The reported case is file() fed by a child module output that is deprecated, wrapped in sensitive(). Plan recorded a Create for terraform_data. During apply, about half of repeated runs failed with function file returned an inconsistent result. The bug is the comparison, so any function whose result carries two or more marks can hit it. The report includes 1.15.9, 1.16.0, and 1.16.1.
#39170 builds a stable string from the cty value, keeping each mark and the path where it sits, and compares those strings. Arguments use IsWhollyKnown, since an applied value has no nested unknowns. Applies that flipped between success and an inconsistent function result should stay on the success path.
Chained mark filters failing validation ¶
marks.PathsWithMark kept the mark it had just selected inside the unmatched PathValueMarks set. A second call, used to filter another mark type, saw that mark again. The release notes describe the result: values with multiple marks erroneously fail validations.
On 1.16.1 the failure is a hard error while encoding a child module output. A nested attribute such as primary_access_key, marked sensitive by the provider schema, under a deprecated resource, produced a serialization error from Change.Encode:
Error: new value .value: can't serialize value marked with cty.NewValueMarks(marks.Sensitive, marks.deprecation<Deprecated resource used as value. Refer to the provider documentation for details.>) (this is a bug in Terraform)
terraform test with mock_provider failed. The same configuration on 1.15.9 emitted the deprecation warning and passed. Change.Encode in internal/plans/changes.go calls PathsWithMark for the sensitive mark and then for the deprecation mark. After the first call the sensitive mark was still in the unmatched set, so the pair still looked unsupported. Each mark alone encoded. The mark order printed in the error also varied between runs. This failure happens on every run.
#39171 drops the selected mark from the unmatched set, so a later filter does not revive it. Child module outputs that are both sensitive and deprecated can be encoded again.
Import provider resolution ¶
Import blocks with no explicit provider argument stopped using required_providers in 1.16.0. A refactor of GraphNodeProviderConsumer merged ProvidedBy and Provider on NodeAbstractResource. The previous Provider method returned the provider already resolved from required_providers when importTargets was set and the import config existed. The merged method kept only the branch for an explicit provider reference. Other imports fell through to ImpliedProviderForUnqualifiedType, which assumes registry.terraform.io/hashicorp/ plus the resource type prefix.
Operators hit this on terraform plan -generate-config-out=generated.tf, where the address exists only in an import block. A local name declared in required_providers, for example datahub sourced from datahub-project/datahub, was ignored. Core tried to load registry.terraform.io/hashicorp/datahub and failed while reading the schema. A resource block for the same type still resolved the local name. Setting provider = datahub on the import block avoided the bug. v1.15.9 resolved the implicit form correctly.
#39185 restores the implicit import path so the provider from required_providers is the one attached during plan. Schema reads for generated config follow the same provider a managed resource would use.
Where to get it ¶
- Release page: Terraform v1.16.3
- Repository: hashicorp/terraform
- Tag:
v1.16.3