Last reviewed: 2026-07-31
Direct answer
Treat an agent-authored Terraform plan as an inspectable proposal, never as authorization to change infrastructure. Put an enforced boundary between the process that edits or plans and the process that applies. The coding agent can prepare a narrowly scoped patch, and a controlled runner can calculate its effects, but an independent human should approve the final saved plan before a deployment job applies it.
Terraform supports this separation directly. The Terraform plan command reference
says that terraform plan previews proposed changes without carrying them out. It also supports saving a plan for a later terraform apply. That creates a useful control chain:
- Tie the proposed change to one commit and one declared workspace.
- Generate a plan in a controlled runner.
- Convert that plan to JSON and inspect every action.
- Run policy checks against both the configuration and the plan.
- Publish only a sanitized review summary.
- Generate and review the final saved plan from the deployable commit.
- Require a person who did not initiate the run to approve it.
- Apply that exact approved plan artifact.
The agent should not hold the capability to approve or apply. Establish those permission and secret boundaries before adding any Terraform automation.
Who this is for
This workflow is for platform engineers, infrastructure maintainers, and application teams that let coding agents modify Terraform in pull requests. It is especially useful when a seemingly small configuration edit can replace a resource, alter network exposure, change identity permissions, or delete stateful infrastructure.
It assumes that Terraform already runs in a controlled environment with an established backend, workspace convention, provider lock file, and deployment identity. It does not replace provider-specific review rules or incident procedures. The agent still needs a narrow task brief that defines a reviewable change , including the modules and resource classes it may touch.
Key takeaways
- Separate edit, plan, approve, and apply into distinct capabilities.
- Review action semantics, not just the Terraform source diff. A replacement or deletion can be more consequential than the line count suggests.
- Bind every plan to its source commit, workspace, plan mode, policy result, and artifact digest.
- Treat plan files and plan JSON as sensitive artifacts. Do not paste their raw contents into public pull-request comments or ordinary logs.
- Fail closed when a critical value is unknown, a policy check is incomplete, the plan contains an unapproved delete or replacement, or the final commit differs from the reviewed commit.
- Apply only the final saved plan that passed policy checks and human approval.
Happy path: from agent patch to approved apply
Define the boundary. The task brief names the Terraform root module, expected resource addresses, permitted action types, target workspace, and explicit non-goals. The agent works on a branch or isolated worktree and cannot reach the apply identity.
Generate a plan in a controlled runner. Record the source commit, Terraform version, workspace, backend identity, plan mode, and provider lock-file state. The runner creates a saved plan and a JSON representation for machine checks:
terraform init
terraform plan -out=tfplan.bin
terraform show -json tfplan.bin > tfplan.json
checkov -f tfplan.json
The Checkov plan-scanning guide documents this plan-to-JSON workflow. Run the repository’s normal configuration checks as well, because plan scanning and source scanning expose different information.
Evaluate policy. Feed the same JSON to the team’s policy entry point. The Open Policy Agent Terraform guide identifies
resource_changes, resource types, and action arrays as useful inputs for rules over creates, updates, and deletes. Policies should block prohibited resource classes, unexpected destructive actions, and changes outside the declared scope.Review the contract. A reviewer compares the planned addresses and actions with the task brief. The review calls out deletions, replacements, identity changes, network changes, stateful resources, unknown values, ignored checks, and any use of special planning options.
Publish a sanitized summary. Keep raw plan artifacts in restricted storage. A useful operator record contains identifiers, counts, decisions, and digests without raw dynamic values:
run_id: run-042
repository_ref: refs/pull/42/merge
commit_sha: 7f3c2ab
workspace: prod-network
terraform_version: 1.15.x
plan_mode: normal
plan_digest: sha256:9d2e
create_count: 2
update_count: 1
delete_count: 0
replace_count: 0
unknown_value_count: 3
policy_result: pass
approval_result: approved
reviewer_role: platform-reviewer
sensitive_values: '[REDACTED]'
Do not log raw variable values, complete resource payloads, or the full plan JSON. Checkov warns that a plan can contain dynamically injected sensitive arguments, so access to the binary plan, JSON, and detailed scanner output should be restricted.
Create the final apply candidate. After the deployable commit is fixed, generate a fresh saved plan in the deployment environment. Do not promote an old pull-request preview blindly. Re-run the same policy checks and show the final action summary to the reviewer.
Require independent approval. A protected deployment job waits for a human decision. GitHub’s deployment review documentation describes approving or rejecting waiting jobs and supports preventing the initiator from approving their own deployment when the environment is configured accordingly.
Apply the reviewed artifact. Once approved, the deployment stage applies the saved plan rather than generating an unseen replacement plan:
terraform apply tfplan.bin
The deployment record should retain the commit, plan digest, policy result, reviewer, decision time, and final outcome.
Error path: reject, narrow, regenerate
If the plan contains an undeclared delete, replacement, resource class, workspace, or special planning mode, stop before approval. Do the same when a critical value is unknown, a policy tool cannot evaluate required fields, the plan and source commit do not match, or the plan artifact has been exposed beyond its intended audience.
Reject the deployment, invalidate the saved plan, and retain only the sanitized decision record in ordinary logs. Return a focused follow-up to the agent that names the affected resource addresses, failed policy identifiers, and expected action types without copying sensitive values. The agent may then narrow or correct the source change. Generate a new plan from the corrected commit and repeat every check; never approve a plan by editing its summary or reusing evidence from the failed run. If the next action is unclear, use explicit stop, retry, or escalation criteria .
Sources checked
- HashiCorp’s terraform plan command reference establishes that planning previews proposed actions without executing them, distinguishes speculative and saved plans, and recommends checking the final non-speculative plan before apply.
- The Open Policy Agent Terraform guide shows how plan JSON can support policy checks over resource changes. It also notes that computed values, dynamic blocks, and function results may be unavailable at plan time.
- Checkov’s Terraform Plan Scanning documentation explains plan-JSON scanning, deletion and changed-field checks, plan enrichment, ignored checks, and secure handling of plans that may contain sensitive arguments.
- GitHub’s Reviewing deployments documentation explains approval, rejection, protection-rule bypass controls, and prevention of self-approval when configured.
Together, these sources support a layered workflow: Terraform produces the proposal, policy tools test its contract, restricted logs preserve review evidence, and a protected deployment environment supplies the human decision. Provider-specific behavior still needs local verification.
Contract details to verify
A useful coding agent Terraform plan review checks a defined contract instead of asking whether the output merely looks plausible.
Source identity
Verify the repository, immutable commit, Terraform root module, provider lock file, and configuration directory. A plan from another commit or directory is not evidence for the current patch. Regenerate it after rebases, merges, module changes, or provider-lock changes.
State and workspace identity
Record the workspace and the intended backend without printing sensitive backend configuration. Confirm that the plan read the expected state. A clean-looking plan against an empty, test, or wrong regional state can be dangerously misleading.
Planning mode and options
Normal mode should be the default. Require an explicit reason for destroy mode or refresh-only mode. HashiCorp also warns that -refresh=false can produce an incomplete or incorrect plan because it ignores external changes, and that -target is for exceptional circumstances. Treat either option as a review exception, not a convenient shortcut.
Action semantics
Count and inspect creates, updates, deletes, and replacements. A replacement commonly carries both destructive and constructive effects, so do not summarize it as an ordinary update. Compare every changed address with the task brief and require an owner decision for stateful resources, identity controls, network boundaries, and public exposure.
Known and unknown values
OPA documents that some computed attributes and runtime-dependent results are unavailable in plan JSON. Unknown does not mean safe. If a policy decision depends on an unavailable field, route the change to a different validation step or a manual reviewer with provider-specific evidence. Do not silently convert an indeterminate result into a pass.
Policy coverage
Record which policy bundle and configuration checks ran, which failed, and which were skipped or inapplicable. Checkov notes that some checks do not apply to plan files and that combining plan and Terraform scans can improve context. A green plan scan is therefore one signal, not proof that the source configuration has complete coverage.
Approval and artifact identity
The approval must refer to the final plan, not a similar earlier preview. Bind the approval record to the commit and plan digest. Configure the deployment environment so that the run initiator cannot self-approve, and restrict any bypass path. If the commit, state, policy bundle, or saved plan changes, invalidate the approval and start again.
Failure modes
The agent can plan and apply
This collapses proposal and authorization into one identity. A mistaken instruction, compromised dependency, or misunderstood scope can become an infrastructure change without an independent checkpoint. Remove apply capability from the agent’s execution surface and put it behind the deployment gate.
The pull-request plan is reused after the world changes
A plan can become stale after a merge, state change, provider update, or parallel deployment. HashiCorp explicitly recommends checking the final non-speculative plan. Generate a fresh apply candidate from the deployable commit, rescan it, and obtain approval for that artifact.
A replacement is reported as a harmless change
A replacement can delete the current object and create another. Simple totals or a source diff may hide that impact. Surface replacement counts separately and require explicit approval for each affected address.
Unknown values pass policy by default
Plan-time gaps can prevent a rule from evaluating the property it was designed to protect. Mark the result indeterminate and stop. Add another check at the stage where the value becomes knowable, or require documented manual verification.
Raw plan JSON leaks into logs or pull-request comments
Plan JSON can contain dynamically supplied arguments. Keep it in restricted artifact storage, limit retention, and expose only sanitized action counts, addresses approved for review, policy identifiers, digests, and decisions. Replace sensitive values with [REDACTED] rather than attempting partial masking.
Plan scanning is mistaken for complete source scanning
Some configuration information is absent from the plan, and Checkov documents ignored checks and optional enrichment. Run the established source checks and plan checks, then report their coverage separately.
The initiator approves or bypasses the deployment
A button is not an independent control if the same actor can propose, initiate, approve, and bypass. Require an eligible reviewer, prevent self-approval, and disable administrative bypass where the platform and operating model allow it.
Special options conceal the real change set
Targeted planning or disabled refresh can omit context. Block these options by default. If an incident requires one, record the reason, owner, affected resources, and follow-up plan for returning to a complete state review.
FAQ
Can a coding agent run terraform plan safely?
It can run in a bounded environment when the team has deliberately separated planning from applying. The runner should have only the access required to calculate the intended plan, and the agent should not possess the deployment approval or apply capability. Plan output must still be handled as sensitive.
Is a passing policy scan enough to approve the plan?
No. Policy tools can only evaluate the information they receive, and OPA documents plan-time unknowns. Checkov also documents checks that are ignored or need source enrichment. Combine automated policy with scope review, action review, source checks, and an independent approval.
Can the team apply the plan generated for the pull request?
Prefer a fresh final saved plan generated from the exact deployable commit and current state. The pull-request plan is valuable review evidence, but intervening source or state changes can invalidate it. Re-run policy and approve the final artifact that will actually be applied.
Should the full plan JSON be attached to the pull request?
Not by default. Treat the JSON and binary plan as restricted artifacts because dynamic arguments may be present. Put a sanitized summary in the pull request and give authorized reviewers controlled access to detailed artifacts when they need it.
What should happen when the plan contains a deletion?
Compare it with the task brief and require explicit approval for the exact address and consequence. If the deletion was not declared, reject the plan and return a narrow correction task. Do not let a low overall change count override an unexpected destructive action.
What evidence should be retained after apply?
Retain the source commit, workspace, Terraform version, plan mode, action counts, plan digest, policy outcomes, reviewer identity or role, approval decision, and apply result. Keep sensitive plan payloads under restricted retention rather than copying them into ordinary logs.
Reader next step
Start with one Terraform root module and implement the boundary before expanding it:
- Write a change scope note that lists allowed resource addresses and action types.
- Remove approval and apply capability from the agent’s environment.
- Generate plan JSON in a controlled runner and scan both the plan and source configuration.
- Publish the sanitized fields shown above while keeping detailed artifacts restricted.
- Configure an independent deployment reviewer and prevent self-approval.
- Test the error path with an intentionally disallowed deletion in a disposable environment and confirm that no apply stage starts.
The workflow is ready for broader use only when the happy path applies the exact approved saved plan and every error path stops before infrastructure changes begin.