This topic ties the whole module together with one concrete change carried start to finish: adding a second availability zone to an application's autoscaling setup, with an AI agent assisting at every step but never holding the button that applies the change to production. The scenario is deliberately mundane — most real infrastructure changes are this unglamorous — because the discipline that matters is procedural, not clever.
Workflow Overview: From Requirement to Safely Applied Infrastructure Change
The shape of a well-run AI-assisted infrastructure change has four stages, and skipping any of them is where things go wrong regardless of which agent you're using:
- Write the change and generate a plan — HCL edits plus a
terraform plan, with the agent assisting on syntax and cross-referencing existing resources. - AI plan review — a structured pass over the plan JSON specifically hunting for destructive changes, drift, and policy violations, distinct from "does this look right."
- Human-readable PR — the plan output translated into something a reviewer who didn't write the HCL can actually evaluate in a few minutes, not just a rubber-stamp on a wall of JSON.
- Apply, deliberately — triggered by a human (or a gated CI job a human approved), never as a follow-on tool call from the same agent session that generated the plan.
The connective tissue is that the plan file generated in stage 1 is the same plan file reviewed in stage 2, summarized in stage 3, and applied in stage 4 — never regenerate a "fresh" plan right before apply and assume it's equivalent to the one that was reviewed. State drifts between when a plan is created and when it's applied are rare but real, especially in active environments, and terraform apply <planfile> explicitly re-validates against current state before proceeding — if it detects drift, it errors rather than silently applying a stale plan, which is the correct failure mode.
terraform plan -out=tfplan.staging-az2
terraform apply tfplan.staging-az2
Tips
- Treat the plan file as the artifact under review, not the HCL diff or a description of intent — review, PR description, and apply should all trace back to the same-outfile.
- Never let "just regenerate the plan, it'll be basically the same" become a habit before apply —terraform apply <planfile>fails safely on drift for a reason; don't defeat that safety by skipping the saved plan.
- Keep stage 4 (apply) procedurally separate from stages 1-3 even when the same person does all four — a deliberate context switch (opening a terminal, running a named command) is a real, if small, safeguard against acting on autopilot.
Step 1: Writing the Change and Generating a Plan with AI Assistance
The requirement: the application currently runs in a single availability zone; add a second for resilience. Starting HCL:
resource "aws_autoscaling_group" "app" {
name = "acme-app-asg"
desired_capacity = 3
min_size = 2
max_size = 6
vpc_zone_identifier = [aws_subnet.app_az1.id]
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
}
resource "aws_subnet" "app_az1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
}
Prompt for the change with an explicit constraint that matters here — a new subnet needs an explicit CIDR that doesn't collide with the existing one, and the agent has no way to know your full CIDR allocation plan unless you tell it or point it at the state:
Add a second subnet in us-east-1b for high availability. Check
existing subnet CIDR blocks via terraform_state_list and
terraform_state_show first so the new one doesn't collide. Update
the autoscaling group's vpc_zone_identifier to span both subnets.
Resulting HCL, after the agent inspects state to confirm 10.0.1.0/24 is taken:
resource "aws_subnet" "app_az2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1b"
}
resource "aws_autoscaling_group" "app" {
name = "acme-app-asg"
desired_capacity = 3
min_size = 2
max_size = 6
vpc_zone_identifier = [aws_subnet.app_az1.id, aws_subnet.app_az2.id]
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
}
Validate, then generate the named plan file that carries through the rest of this workflow:
terraform validate
terraform plan -var-file=staging.tfvars -out=tfplan.staging-az2
Terraform will perform the following actions:
# aws_subnet.app_az2 will be created
+ resource "aws_subnet" "app_az2" {
+ availability_zone = "us-east-1b"
+ cidr_block = "10.0.2.0/24"
+ vpc_id = "vpc-0abc123"
}
# aws_autoscaling_group.app will be updated in-place
~ resource "aws_autoscaling_group" "app" {
~ vpc_zone_identifier = [
"subnet-0a1b2c",
+ "subnet-0d4e5f",
]
}
Plan: 1 to add, 1 to change, 0 to destroy.
Tips
- Have the agent check existing resource state before proposing new CIDR blocks, tag values, or naming — collisions caught at plan time are cheap; collisions caught at apply time (AWS API rejection) waste a review cycle.
- Name the plan file for what it does (tfplan.staging-az2, nottfplanorout.tfplan) — it's a small thing that makes the next three steps, and any later audit of what got applied when, much easier to follow.
- Runterraform validateas a matter of habit before every plan, even on a change this small — it's near-free and it keeps syntax errors out of your plan review entirely.
Step 2: AI Plan Review — Destructive Changes, Drift, and Policy Violations
This step is a distinct pass, not a rerun of step 1's summary — ask specifically for the three categories in the heading, because a generic "does this look okay" prompt tends to just restate the diff:
Review tfplan.staging-az2 for three things specifically:
1. Any destroy or replace action.
2. Any sign the plan doesn't match current real state (drift).
3. Any violation of our tagging policy — every resource must have
Environment and Owner tags — or our policy that autoscaling
groups must not have min_size below 2.
1. Destructive changes: none. Both actions are create and
in-place update; no destroy or replace present.
2. Drift check: ran a refresh-only comparison against the same
working directory — no drift detected, state matches AWS's
actual reported config for both existing resources.
3. Policy check: FAILS. aws_subnet.app_az2 has no tags block at
all, missing both Environment and Owner. The autoscaling group's
min_size of 2 satisfies the min_size >= 2 rule.
Recommendation: add tags to aws_subnet.app_az2 before proceeding;
everything else in this plan is safe to apply as-is.
Catching the missing tags block here is a genuinely realistic and common miss — it's exactly the kind of policy-conformance check that's tedious for a human to do by memory on every plan and mechanical enough for an agent to do reliably every time, provided you actually state the policy rather than assuming it's obvious. If your organization runs Sentinel or OPA policies against Terraform Cloud/Enterprise runs, this AI review is a complement to that gate, not a replacement — Sentinel policies are the enforced, unbypassable check; the agent's review is a faster feedback loop before you even get there.
resource "aws_subnet" "app_az2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "us-east-1b"
tags = {
Environment = "staging"
Owner = "platform-team"
}
}
Re-run plan after the fix and confirm the new plan file, then proceed with that one — not the earlier one with the missing tags:
terraform plan -var-file=staging.tfvars -out=tfplan.staging-az2
Tips
- State your actual policies explicitly in the review prompt (tagging rules, min/max bounds, naming conventions) — an agent won't infer organization-specific policy from a generic "check for problems" instruction.
- Treat AI plan review as a fast pre-check ahead of enforced policy-as-code (Sentinel, OPA, Conftest), not a substitute for it — the agent's review can miss things a properly written policy catches deterministically every time.
- Regenerate the plan file after any fix identified during review, and make sure the PR in the next step references the corrected plan, not the one that failed review.
Step 3: Opening a PR with a Human-Readable Plan Summary and Applying
The plan JSON is not a PR description — nobody wants to review 200 lines of resource_changes array to understand a two-resource change. Ask the agent to draft the PR body from the reviewed plan:
Write a PR description for this change based on tfplan.staging-az2.
Include: what changed and why, the plan summary, the policy check
result from the review, and a rollback note.
## Add second availability zone for app autoscaling group
**Why:** Application currently runs single-AZ in staging
(us-east-1a only). Adding us-east-1b for resilience ahead of the
planned load test next sprint.
**Plan summary** (tfplan.staging-az2):
- `aws_subnet.app_az2`: create (us-east-1b, 10.0.2.0/24, tagged)
- `aws_autoscaling_group.app`: update in-place, vpc_zone_identifier
now spans both subnets
**Reviewed for:** destructive changes (none), drift (none detected),
tagging policy (fixed — az2 subnet now carries Environment/Owner tags).
**Rollback:** revert this commit and re-apply; removing the second
subnet from vpc_zone_identifier is a safe in-place ASG update, but
confirm no instances are actively running in az2 before the revert
apply to avoid an abrupt instance termination mid-request.
**Plan file:** tfplan.staging-az2 (attached / linked in CI artifact)
Attach the actual plan file (or a CI-generated link to it) rather than just the prose summary — a reviewer who wants to verify the summary against ground truth should be able to, in the same PR, without re-running anything locally:
terraform show -no-color tfplan.staging-az2 > plan-output.txt
gh pr comment --body-file plan-output.txt
Once the PR is approved by a human reviewer, apply happens as its own explicit action — a merge-triggered CI job that runs terraform apply against the exact named plan file, or a person running it locally. Either way, it is not a continuation of the same agent session that drafted the PR:
jobs:
apply:
environment: staging-apply # requires a human approval in GitHub environments
steps:
- run: terraform apply tfplan.staging-az2
terraform apply tfplan.staging-az2
That environment: staging-apply approval gate is doing the same job as Claude Code's ask permission on the apply tool, or Cursor's per-call confirmation — a deliberate human checkpoint between "the plan looks good" and "the plan is now real infrastructure." Whichever tool generated the plan and drafted the PR, this last step should look the same: a named plan file, a human decision, an apply command that isn't chained automatically off the review.
Tips
- Attach the actual plan output to the PR (as a file, CI artifact, or bot comment), not just the AI-generated prose summary — reviewers should be able to verify the summary against source, not trust it blindly.
- Gate the apply step behind an explicit human approval mechanism (GitHub environments, a manual pipeline step, a person at a keyboard) regardless of how confident the plan review was — the gate is the actual control, not the review.
- Include a rollback note drafted from the specific plan, not a generic "revert the commit" — for stateful resources especially, what a revert-and-reapply actually does in practice is worth spelling out before you need it under time pressure.
Tips
Tips
- Carry one named plan file through generation, review, PR, and apply — never substitute a freshly regenerated plan at the apply step and assume it's equivalent to the reviewed one.
- Split AI plan review into explicit categories (destructive changes, drift, policy violations) rather than a generic sanity check — it surfaces real misses, like a missing tag, that a vague prompt won't catch.
- Keep the apply step a deliberate, separately-triggered human action every time, whether that's a CI approval gate or someone running the command themselves — this is the one non-negotiable across every tool and workflow in this module.