August 20, 2025·4 min read·engineeringterraformplanningstacks

Terraform 1.13 checks filesystem inputs across plan and apply

Terraform 1.13 records filesystem function results so apply can reject changed inputs. Its new stacks command delegates execution to a separate plugin.

A file path can remain unchanged while the contents read through it change between planning and application. That creates an input change which is easy to miss when a review concentrates on HCL differences. Terraform 1.13.0, released on August 20, checks filesystem function results for consistency across those phases. It also adds terraform stacks, which exposes stack operations through a plugin.

I rely on Terraform's plan to explain the operation being proposed. Inputs read from local files belong in that explanation even when they are not written inline in configuration. Detecting a changed file result makes that expectation more concrete, especially when planning and application happen in different execution environments.

A function call needs a stable result

The filesystem consistency change extends function-result verification to filesystem functions. Its regression case creates an external file, plans a resource whose value reads that file, and changes the file before application. The test also writes and reads the plan to simulate the saved-plan workflow. Application must then report an inconsistent function result.

That sequence establishes the failure being detected. The configuration still refers to the same path, but the operation no longer sees the same input. A path string is insufficient to establish that the selected file contents are unchanged. The verification therefore concerns the function result, rather than merely whether the same expression appears in both phases.

This is especially relevant to artifacts assembled outside Terraform. If a pipeline rewrites a configuration file after planning, an unchanged file expression can conceal that replacement. Treating the resulting value as an input makes the discrepancy visible where Terraform evaluates it.

The implementation retains hashes for the comparison

The function-results implementation in the release hashes the function name and its arguments to identify a call, then hashes the returned value. A later result for that call is compared with the recorded hash. A mismatch returns an error reporting inconsistency.

The source distinguishes information kept during a current run from information retained in a plan. A current-run result can include the original value to improve diagnostics. Stored plan information retains hashes instead, with a comment explaining that retaining potentially large values would enlarge the plan. This lets the comparison survive a saved-plan boundary without copying every filesystem result into the verification record.

Hashing here supports consistency checking. It does not create a dependency edge to a resource that generates the file, and it does not define a publication process for the file. Those remain separate concerns. A client that detects a changed input has identified a mismatch, not repaired the workflow that produced it.

Filesystem functions still do not schedule file creation

The file documentation available before this release states that the file must exist at the start of the Terraform run. It also explains that functions do not participate in the dependency graph. The function reads UTF-8 text, while filebase64 is available for reading bytes into a Base64 representation.

A local expression can make the external dependency clear to a reader. This example assumes that settings.json already exists in the module directory.

locals {
  application_settings = file("${path.module}/settings.json")
}

Adding a resource elsewhere in the configuration to generate that file would not give the function a normal resource dependency. The new consistency check should therefore not be interpreted as support for arbitrary file generation during apply. It checks the observations made through the function under its existing evaluation model.

The released function registration code attaches immutable-result checking to several filesystem operations. The list includes existence checks, file sets and file-content hashes as well as direct reads. This wider scope makes sense because a changed directory listing can alter an expression's result even when the expression never reads the complete contents of one particular file.

Saved plans make the interval explicit

Terraform's plan command documentation describes saving a plan with -out and passing that file to apply later. The two commands make the interval between deciding and executing visible in the workflow. They also make it possible for another process to alter local inputs during that interval.

terraform plan -out=tfplan
terraform apply tfplan

For a pipeline, the practical implication is that the reviewed operation includes more than the configuration checkout. Files consumed by functions need to represent the same intended inputs when application evaluates them. An artifact identifier and a path solve different problems. The identifier can describe what was published, while the path tells the process where to find the bytes it will actually read.

This is a useful additional failure when an input has changed. Continuing silently would make the resulting operation harder to relate to the plan that was approved. A consistency diagnostic identifies a reason to reconstruct the intended input set and produce the appropriate plan.

Stacks introduces another executable boundary

The stacks command implementation delegates execution to an internal plugin. It initializes the plugin, connects through the plugin protocol and forwards the command arguments with output streams. The stacks entry in the main executable consequently does not imply that every stack subcommand is implemented inside that executable.

The original stacks command change defines the client and protocol used for that delegation. This is a different kind of extension from the filesystem check. The latter tightens a core evaluation guarantee, while the former supplies a route to operations implemented across another executable boundary.

As a provider author, I want those boundaries to remain visible when explaining behavior. A Terraform version identifies the core release, but a delegated command can also depend on its plugin implementation. Terraform 1.13's filesystem validation can be understood through the function-result record in the plan, while its stacks behavior needs to be understood through the command and the plugin it invokes.