A configuration review can be technically correct and still be difficult to verify because a one-line expression has been compressed into a shape that hides its arguments, references, or closing braces. It is tempting to make tofu validate the first automated check, but validation has a different dependency boundary: OpenTofu requires an initialized working directory with its referenced providers and modules available. Formatting does not.
The useful first gate is tofu fmt -check -diff. It reads local configuration, reports canonical-format drift, shows the change it would make, and does not rewrite the files. That makes it suitable for a pull-request or pre-commit step before a job initializes a backend, downloads providers, or obtains cloud credentials.
This article validates that boundary with OpenTofu 1.13.1 on Linux amd64. The release archive matched the project’s published SHA-256 manifest before it was extracted. A formatted three-block fixture returned exit status 0. The same declarations with spacing removed returned exit status 3 and a unified diff when checked with -check -diff; the fixture hash was unchanged afterward. That last point matters: -check is a decision gate, not an automatic formatter.
Put formatting before initialization
OpenTofu’s formatter applies a canonical style to supported configuration files. It is deliberately opinionated, and its exact choices can change slightly between minor releases. That makes a pinned binary important: a contributor and the CI job should not silently apply different formatters to the same change.
Formatting is not validation. It will not prove that a provider argument exists, that a module input has the intended type, that a remote object is reachable, or that a plan is safe to apply. Its smaller claim is valuable precisely because it has a smaller dependency surface. A job can reject formatting drift without a .terraform or .tofu directory, a provider mirror, a module download, backend access, or cloud credentials.
Place the formatting job before tofu init in CI. If the configuration is not canonically formatted, there is no reason to allocate a runner with registry or cloud access. If it is formatted, the next job can initialize deliberately, often with a read-only lock-file policy and backend settings appropriate to the environment.
Do not use a formatting pass as permission to skip later checks. A sensible sequence is format, initialize in an isolated CI workspace, validate, and then plan against the explicitly selected workspace and variable set. Each command answers a different question.
Obtain the exact formatter
Use a versioned OpenTofu binary rather than whatever happens to be installed on the runner. The following Linux amd64 example verifies the release archive before extracting it. Run it in a dedicated tool-cache or disposable workspace, not in an infrastructure repository that might later be packaged as an artifact.
set -eu
version=1.13.1
archive="tofu_${version}_linux_amd64.zip"
base="https://github.com/opentofu/opentofu/releases/download/v${version}"
curl -fLO "$base/$archive"
curl -fLO "$base/tofu_${version}_SHA256SUMS"
grep " $archive$" "tofu_${version}_SHA256SUMS" | sha256sum -c -
unzip -q "$archive" -d .tools/tofu
.tools/tofu/tofu version
The checksum command must report OK before unzip runs. The version command should identify both the OpenTofu version and the platform. Pin the archive version in the CI definition or tool-image build. Fetching an unpinned current release during each infrastructure job makes a formatting failure difficult to reproduce after the upstream formatter changes.
OpenTofu 1.13.1 was released on 1 October 2026. Its release notes include fixes for JSON output in configurations containing ephemeral resources and for re-evaluating ephemeral outputs during apply. Those fixes are not a reason to alter a live infrastructure workflow without review, but they are a reason to record the binary used for all pre-apply checks.
Make the check read-only and reviewable
Run the gate at the root of the configuration that a change affects:
set -eu
<path-to-tofu> fmt -check -diff -recursive .
-check implies a non-writing mode. A zero result means the files OpenTofu considered are already in canonical form. A nonzero result means at least one file needs formatting or the command could not complete. Preserve the exit status; do not put this command behind a wrapper that reports success merely because it captured output.
-diff makes a failed review actionable. Without it, the command lists the file that needs attention. With it, the reviewer can see whether the change is only whitespace and alignment or whether a generated or unexpected file has entered the directory. The diff is useful in a pull-request log, but avoid running the command over a directory that can contain credentials in comments, generated variable files, or other material that should not appear in logs.
-recursive is a policy decision. By default, fmt processes the named directory but does not descend into subdirectories. Use recursion only when the repository treats all nested modules as one formatting boundary. A mono-repository may be better served by one job per module root so that an unrelated module does not block a small change.
The formatter includes .tf, .tofu, .tfvars, .tftest.hcl, and .tofutest.hcl files after applying its filename exclusions. Files beginning with . or ~, and files surrounded by #, are skipped. Do not rely on those exclusions to hide a real configuration file from review. Keep temporary and generated files outside the checked module tree, or make their ownership and formatting policy explicit.
Prove both sides of the gate
A passing fixture alone does not show that CI will stop on drift. Test one formatted file and one deliberately unformatted file in a disposable directory. This fixture has no providers, resources, module downloads, backends, or credentials:
# good/main.tf
terraform {
required_version = ">= 1.13.0"
}
variable "environment" {
type = string
}
locals {
label = "review-${var.environment}"
}
The decisive checks are:
<path-to-tofu> fmt -check -diff good
<path-to-tofu> fmt -check -diff bad
In the validation run, the formatted fixture produced no output and exited 0. The unformatted fixture exited 3 and produced this representative diff:
-terraform { required_version=">= 1.13.0" }
-variable "environment" {type=string}
-locals {label="review-${var.environment}"}
+terraform { required_version = ">= 1.13.0" }
+variable "environment" { type = string }
+locals { label = "review-${var.environment}" }
The nonzero value is the contract a CI step should enforce; do not depend on the particular value 3 across OpenTofu releases. The important observation from this run is that -check returned a failure while leaving the unformatted file unchanged. Confirm that property once in the exact tool image used by the pipeline, especially if a wrapper, task runner, or build-system plugin sits between CI and the command.
To repair a real change, run the formatter deliberately in a local branch, inspect the resulting Git diff, and commit the formatting change with the configuration edit or as a separate reviewable change:
<path-to-tofu> fmt -recursive .
git diff --check
git diff -- .
Do not run the writing form in a shared deployment workspace or as an unattended step against a directory that might contain generated files. A gate should make drift visible; its repair should remain a reviewed source change.
The finished boundary is small but useful. tofu fmt -check -diff can stop formatting drift before a workflow needs providers, modules, remote state, or credentials. Follow it with initialization and validation for semantic correctness, then a carefully scoped plan for the environment that will actually change.