ctx: prod
back to posts
$kubectl describepost/terragrunt-duplicate-dependency-labels

Terragrunt read one of your two dependency blocks and said nothing

There is a category of bug that does not crash, does not warn, and passes review. The tool reads your configuration, understands it differently than you do, and proceeds confidently. You find out weeks later, when something is running somewhere it should not be.

Terragrunt shipped one of those for years. Two dependency blocks with the same label parsed without complaint, and every reference to that label quietly resolved to whichever block happened to come last. Version 1.1.4, released on 27 August 2026, finally makes it say something.

The 60-second version — flip through the deck8 slides · swipe →
The config

Two regions declared, one read

Two dependency "vpc" blocks. Every reference resolves to the last one.

The silence

Nothing anywhere

Nothing on parse, nothing in plan, nothing in apply. The first block simply vanishes.

The class

Understood you differently

Same shape as a plan that replaces prod because a resource address changed, not the resource.

The fix

A strict control

Warning by default in 1.1.4. An error with --strict-control duplicate-dependency-labels.

The move

Put it in CI now

Terragrunt's own guidance: run strict controls in non-prod so breakage surfaces before it is the default.

takeaway

Loud versus quiet

A crash tells you where it broke. A silent misread lets you ship it.

The configuration

Here is the whole bug. It fits on a screen, which is part of why it survived so long.

dependency "vpc" {
  config_path = "../vpc-us-east-1"
}

dependency "vpc" {
  config_path = "../vpc-us-west-2"
}

inputs = {
  # Reads ../vpc-us-west-2. Always did.
  vpc_id = dependency.vpc.outputs.vpc_id
}

Read that as a reviewer. Two regions are declared. The intent is obvious and the syntax is valid. Nothing about it suggests that one of those blocks is decorative.

What actually happened is that HCL let the second block shadow the first, Terragrunt kept the last one it parsed, and dependency.vpc meant us-west-2 from then on. The us-east-1 block still ran its dependency, still got planned, still showed up in the graph. Its outputs just never reached anything.

how it plays out
Someone adds a second region by copying the dependency block and changing the path.
The label is not changed, because the label looked like a type, not an identity.
Terragrunt parses both, keeps the last, and says nothing.
Plan is clean. Review passes. The config reads correctly to a human.
A resource lands in the wrong region, and now you are reading HCL at an unpleasant hour.

Why this shape is the dangerous one

I have written before about the other end of this category. On a client migration, terraform plan wanted to replace a production database. Nobody had typed destroy. The module had been swapped, the resource address had changed, and Terraform tracks resources by address rather than by what they are, so a changed address reads as delete-then-create. The plan was correct. It was also one apply away from taking the database with it.

Same shape as this one. The tool did exactly what it was told. What it was told was not what anyone meant.

two kinds of wrong
Loud failureA crash, a failed apply, a validation error. It stops, and it tells you where. Annoying, cheap.safe
Silent misreadValid syntax, confident execution, different meaning. It passes review because there is nothing to see.expensive

Loud failures are a solved problem. Every pipeline in the world catches those. The ones that cost real money are the configurations that are wrong and legible at the same time.

The fix

Terragrunt 1.1.4 warns when it finds duplicate labels. If you want it to stop, there is a strict control:

terragrunt run plan --strict-control duplicate-dependency-labels
/path/to/terragrunt.hcl: dependency vpc is declared more than once; every dependency needs an address of its own

The error names the address the blocks are fighting over, which is the useful half. Fixing it is mechanical: give every block a label of its own. If a configuration was relying on the shadowing, deliberately or otherwise, keep only the block that was winning and delete the rest, because that is what was running.

Put the flag in CI before you need it

Strict controls in Terragrunt are a staged deprecation mechanism. A behaviour becomes a warning, then an error under strict mode, then eventually the only behaviour. The documentation's own recommendation is to enable strict controls in non-production pipelines so incompatibilities surface early, while you still get to choose when to deal with them.

  • Add --strict-control duplicate-dependency-labels to the non-prod plan job first, not to prod.
  • Run it once across every unit. If nothing fires, the flag costs you nothing and stays as a guard.
  • If it fires, treat each hit as an audit question rather than a rename: which block was actually running, and is that the one you wanted?
  • Only then promote the flag to the production pipeline.

The point of the middle step is that renaming the label changes behaviour. A duplicate that has been shadowing since 2024 means half your configuration has been dead code, and the moment you give both blocks real labels, the previously-ignored one wakes up and starts feeding real values into real resources. That is a change, and it deserves a plan you read carefully.

A crash tells you where it broke. A silent misread lets you ship it.

Also in 1.1.4

One smaller change in the same release, worth knowing if you scaffold from the command line. terragrunt scaffold used to write # TODO placeholders for every input and leave you to fill them in, while scaffolding the same component from the Catalog TUI opened a form and collected the values properly. Now the command opens that same form.

ContextBehaviour
Interactive terminalOpens the form, lists the source's variables or values.* references
--non-interactivePlaceholders, exactly as before
stdin is not a terminalPlaceholders, exactly as before
Source asks for nothingSkipped

The CI-safety design is the part I appreciate. A scaffold running inside a pipeline, or driven by another program, behaves exactly as it did. Nobody's automation breaks because an interactive nicety was added.


Go grep your Terragrunt repos for duplicate dependency labels before the flag does it for you. If you find one, I would like to hear how long it had been there. Find me on LinkedIn.