Terraform, Terragrunt and CDK for Terraform are the OpenTofu adapter with another binary or a wrapper in front
Decision record 0068
The build plan’s slice 5.1 brings the Terraform family behind the OpenTofu adapter: the terraform binary, Terragrunt, and CDK for Terraform. docs/later.md held two lines for it. One said Terraform “is drifting from” OpenTofu’s plan format and needs its own recordings and floor. The other said Terragrunt and CDK for Terraform reduce to the same plan JSON. This record settles how each one is declared, run and checked, from the tools’ own help and docs and from what terraform v1.14.0 and v1.16.3, terragrunt v1.0.0 and v1.1.6 and cdktf v0.21.0 printed on 2026-09-22, recorded on CI runners.
Decision
One adapter, one plan format. All three end in a plan file that the OpenTofu adapter already reads with show -json, and in a deploy of exactly that file (record 0053). The fold, the paths, the sensitive marks, the value list and the saved plan stay as record 0053 settled them. What changes is which binary runs, what stands in front of it, and in which directory.
tool: terraform names the binary. An entry with tool: terraform declares a root module exactly as tool: opentofu does, with the same options, and every command runs terraform in place of tofu. A Terraform root module is a directory with *.tf or *.tf.json files, because Terraform reads no .tofu file. Replaying the twenty scenarios both tools share, the preview of each Terraform recording gives the diff that tofu v1.12.6 gave for it, change by change, on both Terraform versions. The one difference found: Terraform adds applyable and complete to the plan. complete: false means Terraform left changes for a later plan, so the plan shows only part of what a deploy would do. Such a plan is output Sluiceway cannot read, never a diff and never in sync, like an errored plan. No action list outside record 0053’s table was seen, and an unknown one still fails the preview.
A wrapper stands in front of the tool, as the named option wrapper. It takes terragrunt or cdktf, on an entry of either tool, so a team picks the binary once with tool and the wrapper does not care which. Every entry of one directory names the same tool and wrapper, because they share the directory’s init. A mismatch is a config problem.
A Terragrunt unit is one stack. path is the unit, a directory with terragrunt.hcl or terragrunt.hcl.json, and its stack id is path or path:name as before. terragrunt run --all is never used: a run over many units is many deploys behind one box. Every command is the tool’s own command behind terragrunt run --tf-forward-stdout --no-color --no-auto-init --tf-path <binary> --, run in the unit’s directory:
--tf-pathnames the binary of the entry’s tool, so the unit runs what the entry says, whateverTG_TF_PATHsays.--tf-forward-stdout: Terragrunt otherwise folds the tool’s output into its own log (its help, “run”), and the plan JSON has to stay JSON.--no-auto-init: an init inside a preview would run side by side with other previews, which record 0053 forbids. The init runs as the unit’s preparation, through Terragrunt, one at a time. Terragrunt v1.0.0 and v1.1.6 print a warning that init is needed on every plan of a unit whose code comes fromsource, and the plan works. The warning goes to the job log.- No
--non-interactive: it answers yes to every prompt (its help), and a preview must not say yes to anything a person would be asked about. Sluiceway gives the tool no stdin. A repo that wants the prompts answered setsTG_NON_INTERACTIVEin the workflow, where it is the repo’s choice (record 0015). Both recorded versions checked that--tf-pathwins overTG_TF_PATH, and cdktf v0.21.0 that--outputwins over theoutputofcdktf.json.
The unit’s var files and inputs are in its terragrunt.hcl, so the entry takes no varFiles, and a unit’s code usually lives in another directory, which the entry claims with inputs. A dependency block of the unit is read by Terragrunt for its outputs and is not a Sluiceway dependency. The floor is v1.0.0, the first release of the command line these flags belong to. It was recorded with v1.0.0 and v1.1.6, with tofu v1.12.6 behind it.
A stack of a CDK for Terraform app is picked by its name. path is the app, a directory with cdktf.json. The stacks of an app exist only in its program, so the entry’s name is required and picks the one it deploys, and the stack id is always path:name. This amends record 0053, where a name is only a label: here the name is what the app calls the stack, written to cdktf.out/stacks/<name>, so it must be letters, digits, - and _. The preparation of an app runs cdktf synth --output cdktf.out in the app’s directory, once for all its stacks, and then the tool’s init in the directory of each stack at hand, in name order, one at a time. A failed synth is a preview failure of every stack of the app, and no init runs. The plan, the tool diff and the deploy run in the stack’s directory. --output is passed so that no cdktf.json setting moves the stacks from where the adapter looks. The app sets its variables in code, so the entry takes no varFiles.
cdktf synth runs the app, the command cdktf.json names, the way pulumi preview runs a Pulumi program. The workflow installs cdktf and whatever the app’s language needs (record 0013). HashiCorp archived CDK for Terraform in December 2025, and v0.21.0 of June 2025 is its last release. It is the floor and the only recorded version. An archived tool still synthesizes, and Sluiceway needs nothing of it but synth: the plan and the deploy are the tool’s own. cdktf-cli installs with a native module that failed to build on Node 23 on a laptop and built on Node 22 and on the runner’s Node 24.
The version check runs per binary. tofu version -json for OpenTofu stacks, terraform version -json for Terraform stacks, terragrunt --version when a stack has the Terragrunt wrapper and cdktf --version when a stack has the CDK for Terraform one, each once per job and only when a stack at hand needs it. The Terraform floor is v1.14.0: HashiCorp’s support policy fixes the current release line and the two before it, which on 2026-09-22 are 1.16, 1.15 and 1.14. Everything the adapter reads was recorded with v1.14.0 and v1.16.3.
The environment is unchanged. Every TF_* and TG_* variable of the job reaches the tools, and Sluiceway sets TF_IN_AUTOMATION and, from the options, TF_WORKSPACE, and nothing else (records 0013 and 0053). The one TG_* setting Sluiceway decides, the binary, is a flag, and --tf-path wins over TG_TF_PATH.
Consequences
- Amends 0053:
tool: terraformsits next totool: opentofu, the named optionwrapperis new, the name of a CDK for Terraform entry picks a stack, and a Terraform plan must say it is complete. - The check lists these stacks as it lists every declared stack, and stops on a bad entry with the same messages a scan gives (record 0042).
- The recorder has three more tools, and CI a recorder job for each:
fixtures-terraform,fixtures-terragruntandfixtures-cdktf. The mixed end to end run deploys a Terraform stack, a Terragrunt unit and a stack of a CDK for Terraform app with the real tools. - Not in this version (
docs/later.md):initwriting Terraform, Terragrunt or CDK for Terraform stacks and their setup steps,dependsOn: autofrom a unit’sdependencyblocks,varFileson a Terragrunt unit, and zero-config discovery of Terragrunt units fromterragrunt.hcl.