On vibe coding your infrastructure

What happens when you point AI at your Terraform, and why infra-from-code frameworks are the structural fix.

After I wrote about letting the AI cook on application code, a few people came back with the same pushback. Sure, that works for the route handlers and the database queries, but what about the infrastructure? Are you handing Terraform to the model and just letting it run?

I'm not, and that's the place where the whole approach breaks down for me. The model writes HCL fine, that part has never been the issue. The hard part of infrastructure work is the decisions that go into each line, like sizing and retention and IAM scopes and timeouts, and the application code gives the model almost nothing to work with on any of them. Should this SQS queue have a visibility timeout of 60 seconds or 600? Should the IAM role have s3:* or be scoped to a specific bucket? Should retention be set to default or set to fourteen days because someone got bitten by a replay window last quarter? The application code doesn't say, and the model ends up making the call blind.

Here's what asking an agent to add a single event to an Express + Terraform stack looks like on the infrastructure side, and that's assuming the first attempt is clean, which it usually isn't:

Terraform
resource "aws_sns_topic" "order_created" {
  name = "order-created"
}
 
resource "aws_sqs_queue" "order_created" {
  name                       = "order-created"
  visibility_timeout_seconds = 60
  message_retention_seconds  = 1209600
  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.order_created_dlq.arn
    maxReceiveCount     = 5
  })
}
 
resource "aws_sqs_queue" "order_created_dlq" {
  name                      = "order-created-dlq"
  message_retention_seconds = 1209600
}
 
resource "aws_sns_topic_subscription" "order_created_to_queue" {
  topic_arn = aws_sns_topic.order_created.arn
  protocol  = "sqs"
  endpoint  = aws_sqs_queue.order_created.arn
}
 
resource "aws_sqs_queue_policy" "order_created_allow_sns" {
  queue_url = aws_sqs_queue.order_created.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { Service = "sns.amazonaws.com" }
      Action    = "sqs:SendMessage"
      Resource  = aws_sqs_queue.order_created.arn
      Condition = {
        ArnEquals = { "aws:SourceArn" = aws_sns_topic.order_created.arn }
      }
    }]
  })
}
 
resource "aws_iam_role_policy" "orders_publish_order_created" {
  role = aws_iam_role.orders_service.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect   = "Allow"
      Action   = "sns:Publish"
      Resource = aws_sns_topic.order_created.arn
    }]
  })
}

That's six AWS resources for one event, and every number and every scope in there is a decision the agent is making without the context to make it correctly. The retention defaults to fourteen days and the visibility timeout to 60 seconds because those numbers came up in the training data, with no reasoning about the actual workload behind either choice. The IAM policy looks plausibly scoped until you ask whether the consuming service's role even exists in this account, which the agent has no way to verify from the file it's editing.

Reviewing that PR ends up taking more skill and more time than reviewing the application code that motivated it. Code review used to mean reading a few hundred lines of typed code that did one thing, and now it means cross-checking HCL against AWS IAM semantics against your existing pipeline against your team's tacit knowledge about how the service interacts with the rest of the system. The reviewer has to carry all the context the agent didn't, and the cost of missing something is a production outage at three in the morning rather than a failing test on CI.

The usual response is to layer more tooling around the agent in the hope that some service catalog or custom abstraction module catches the mistakes before they ship, but no amount of that closes the gap, because the gap isn't really a tooling problem. As long as the application code and the infrastructure it depends on live in two separate repos with two separate review cycles, the agent will keep making decisions in one half that depend on context it can't see from the other half, and reviewers will keep failing to catch the mistakes.

The fix is to remove the seam between application and infrastructure entirely. When new Topic("order-created") is a one-line declaration that compiles down to a real broker, with the IAM and the dead-letter queue and the subscription all wired by the framework based on what it can see in the typed code, the agent isn't making infrastructure decisions in isolation, because the infrastructure is downstream of the application code it's already looking at.

This is why I work at Encore, and I'll be upfront about that. The same order-created event is three lines:

TypeScript
export const orderCreated = new Topic<OrderCreatedEvent>("order-created", {
  deliveryGuarantee: "at-least-once",
});

The framework provisions the broker, generates the IAM scoped to the publishing service, wires the dead-letter queue, and the consuming subscriber gets type-checked against the topic shape at compile time. The PR is one TypeScript diff instead of a TypeScript diff plus fifty lines of HCL plus an IAM policy you have to audit by hand. The framework is doing the dangerous work, which means the agent can't get it wrong because the agent isn't the one doing it.

The same pattern covers the rest of what an agent would normally have to wire by hand. None of that work needs a separate repo or a separate review cycle, because all of it becomes a declaration in the application code that the framework compiles down. The defaults are sensible enough to ship without thinking about them, and when you do have a real reason to set the visibility timeout to 600 seconds or pin the database to a specific instance class, the override is one field on the declaration rather than a fresh block of HCL in a separate repo.

The vibe coding works when the framework around the agent is the thing making the dangerous decisions, and the agent is just expressing what the application needs. Pick a stack where the seam between app and infra is already removed, and the question of whether to vibe code your infrastructure stops being a question, because there isn't really any infrastructure left for the agent to vibe code. The agent is writing application code, and the cloud is downstream of that.