# Terraform Anti-Patterns (Bad Patterns) Seven common anti-patterns that the Terraform skill explicitly prevents. Each pattern maps to a specific failure mode and is a known LLM hallucination risk. ## 1. List-Driven `count` for Mutable Identities ```javascript variable "queue_names" { type = list(string) } resource "aws_sqs_queue" "worker" { count = length(var.queue_names) name = var.queue_names[count.index] } ``` **Why this fails:** Reordering list entries forces unexpected replacements. Object identity is tied to index, not business key. **Failure mode:** Identity churn. ## 2. No Type Constraints on Critical Input ```javascript variable "network" { default = {} } ``` **Why this fails:** Consumer mistakes surface late and noisily. Module contract is ambiguous. **Failure mode:** Blast radius from silent misconfiguration. ## 3. Sensitive Defaults Committed in Code ```javascript variable "api_token" { type = string default = "token-please-change" } ``` **Why this fails:** Secret can leak via VCS and logs. Violates basic secret hygiene. **Failure mode:** Secret exposure. ## 4. Floating Provider Versions ```javascript terraform { required_providers { azurerm = { source = "hashicorp/azurerm" } } } ``` **Why this fails:** Pulls latest provider implicitly. Increases non-deterministic CI behavior. **Failure mode:** CI drift. ## 5. Blanket `ignore_changes` ```javascript resource "aws_db_instance" "main" { identifier = "core-db" engine = "postgres" lifecycle { ignore_changes = all } } ``` **Why this fails:** Masks drift and important config regressions. Erodes trust in plan output. **Failure mode:** CI drift and blast radius. ## 6. Dynamic Block with Wrong Iterator Reference ```javascript variable "ports" { type = list(number) } resource "aws_security_group" "app" { name = "app-sg" dynamic "ingress" { for_each = var.ports content { from_port = ports.value # WRONG: should be ingress.value to_port = ports.value # WRONG: should be ingress.value protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } } } ``` **Why this fails:** The iterator name defaults to the dynamic block label (`ingress`), not the variable name. Using `ports.value` causes an unknown reference error. **This is a common LLM hallucination pattern.** ## 7. Hidden Ordering via Unrelated `depends_on` ```javascript resource "aws_iam_role" "app" { name = "app-role" } resource "aws_cloudwatch_log_group" "app" { name = "/app/runtime" depends_on = [aws_iam_role.app] } ``` **Why this fails:** Artificial dependency reduces parallelism. Hides poor interface boundaries. **Failure mode:** Blast radius from hidden coupling.