OpenTofu 1.13 Ships Built-In Linting and Clearer Plans
Your plan has forty (known after apply) lines and you've trained your eyes to skip every one. OpenTofu 1.13, released at the end of September 2026, gives you tools to shrink that list, plus an experimental linter that now lives inside the binary.
What OpenTofu 1.13 ships
The v1.13.0 release post covers four additions:
- Built-in linting (experimental), switched on with a
-lintflag. convert()and theassume...functions, which give OpenTofu hints so plans show fewer unknown values.- Symbol libraries (experimental), reusable sets of values, functions and type aliases.
- Official Windows ARM64 packages.
Try OpenTofu linting with the -lint flag
The linting post calls this a first iteration: the plumbing for enabling rules and reporting results as warnings, plus four starter rules. It is experimental and subject to change, so keep it away from anything you can't afford to break.
core:no-type-variable: input variables with notype.core:count-instead-enabled: places whereenabledshould replacecount.core:unused-variable: declared input variables nobody uses.core:unused-local: declared locals nobody uses.
tofu version
tofu validate -lint=all
tofu plan -lint=all
tofu plan -lint=core:unused-variable,core:unused-local
all runs every rule, and a comma-separated list picks specific ones. Findings arrive as warnings, so they won't fail a pipeline on their own. The plan command docs say wrongly formatted values are silently ignored, so run it once against a config with a known unused variable before you trust a clean result. Configuration-file-based rule setup is planned for later.
Cut known after apply noise in tofu plan with convert and assume
When a value is unknown until apply, every test you run on it (is it null, does it start with a prefix, how long is the list) is unknown too. The new functions let a module author state what they know to be true.
convert() tells OpenTofu what type to expect when it can't predict one:
output "example" {
value = convert(jsondecode(something_dynamic.example.output_json),
object({
id = string
tags = map(string)
})
)
}
The assume... family hints at the value itself. It includes assumenotnull, assumestringprefix, assumeequal, and list, set and map length variants:
output "vpc_id" {
value = assumenotnull(
assumestringprefix(
aws_vpc.example.id,
"vpc-",
)
)
}
If an assumption turns out false, the function raises an error instead of passing a bad value along. Per the assume functions docs, assumenotnull needs a known type, so when the type is unknown too, combine it with convert, as in assumenotnull(convert(local.dynamic_example, string)).
Check the OpenTofu 1.13 upgrade notes before you bump
base64gzipoutput changed. It is equivalent but not byte-for-byte identical, because of the required Go version upgrade. Arguments built with it can show up as changes in your plan. A temporary workaround:
resource "aws_instance" "gpu_worker" {
ami = var.ami_id
instance_type = var.instance_type
user_data_base64 = base64gzip(file("${path.module}/cloud-init.yaml"))
lifecycle {
ignore_changes = [user_data_base64]
}
}
ignore_changes also hides genuine edits to that file, so remove it as soon as you can accept the one-time diff.
- WinRM is gone from the
remote-execandfileprovisioners. A connection withtype = "winrm"errors, so move to OpenSSH for Windows first. - macOS 13 Ventura is the new minimum.
- 32-bit builds are ending. 1.13 is the last series with official
386andarmpackages,tofu initwarns on those platforms, and v1.14 drops them. - The 1.13 series is supported until August 2027.
Symbol libraries and Windows ARM64 can wait
OpenTofu symbol libraries are experimental, so don't build shared modules on them yet. Windows ARM64 packages are official, and most major cloud provider builds include windows_arm64, but third-party providers may not. Run tofu init on an ARM64 machine before you promise anyone it works.
What to do on Monday morning
- Search every repo for WinRM usage:
grep -rn 'winrm' --include='*.tf' .
- On a branch, bump the binary and run
tofu planon your biggest root module. Look forbase64gzipdiffs. - Run
tofu plan -lint=allon one module and clean up whatever it flags. - Pick the module with the most unknown outputs and add one
convertorassumecall. Pin the version in any module that uses them, since older releases don't have these functions:
terraform {
required_version = ">= 1.13.0"
}
If you run infrastructure under AI workloads in production, test this on a branch this week: the lint warnings are cheap signal, and the base64gzip change is the one most likely to surprise your plan.