📰 AI NewsAIIntermediate✨ AI-assisted

OpenTofu 1.13 Ships Built-In Linting and Clearer Plans

WittyTech Daily··4 min read
#opentofu#iac#linting

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 -lint flag.
  • convert() and the assume... 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 no type.
  • core:count-instead-enabled: places where enabled should replace count.
  • 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

  • base64gzip output 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-exec and file provisioners. A connection with type = "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 386 and arm packages, tofu init warns 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

  1. Search every repo for WinRM usage:
grep -rn 'winrm' --include='*.tf' .
  1. On a branch, bump the binary and run tofu plan on your biggest root module. Look for base64gzip diffs.
  2. Run tofu plan -lint=all on one module and clean up whatever it flags.
  3. Pick the module with the most unknown outputs and add one convert or assume call. 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.

← More in AI