Terragrunt Updates & Release Notes

Follow

111 updates curated from 1 source by the Releasebot Team. Last updated: Sep 24, 2026

Get this feed:
  • Sep 24, 2026
    • Date parsed from source:
      Sep 24, 2026
    • First seen by Releasebot:
      Sep 24, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.2.0-rc1

    Terragrunt ships v1.2.0 release candidate with major new capabilities, including OCI module sources, non-interactive catalog output, runtime profiling, Azure state backend improvements, direct dependency reads from state, expansion blocks, and an MCP server for AI agents.

    v1.2.0 Release Candidate

    This is the first release candidate for Terragrunt v1.2.

    This release completes the following experiments:

    • block-iteration
    • oci
    • bounded-discovery
    • catalog-format
    • mutable-generate
    • optional-dependency-outputs
    • optional-hooks
    • azure-backend
    • version-attribute
    • profiling
    • dependency-fetch-output-from-state

    Future release candidates for v1.2.0 will include bug fixes related to these experiments or other urgent bug fixes as necessary, and documentation improvements.

    Please try out this release candidate in lower environments and share your feedback in the Associated GitHub discussion.

    Breaking Changes

    base64gzip() uses the Go 1.27 encoder

    Go 1.27 changed the compressed output of its gzip encoder. Terragrunt v1.1.5 kept base64gzip() on the older output and warned once per run that this was legacy behavior. base64gzip() now returns what the Go 1.27 encoder produces.

    A resource that compares the encoded value can plan a replacement on the first run after upgrading. An aws_instance with user_data_base64 set from base64gzip() and user_data_replace_on_change set to true is one such resource.

    Where the encoded value has to stay stable, call the experimental base64gzip_compat(), which returns the v1.1.3 output permanently. It is behind the base64gzip-compat experiment and may be renamed or removed:

    terragrunt run --all --experiment base64gzip-compat -- plan
    inputs = {
    user_data_base64 = base64gzip_compat(file("${get_terragrunt_dir()}/user-data.sh"))
    }
    

    This completes the legacy-base64gzip strict control.

    Note

    Within the 1.0 guarantees

    The 1.0 guarantees make promises about how Terragrunt remains backwards compatible. This change does not break those promises. It is listed under breaking changes so you are aware of it, in case it affects your workflows.

    base64gzip() still takes a string and returns valid gzipped base64 content that decompresses to the same value, but the encoded value itself changes. base64gzip_compat() keeps the old one.

    S3 state buckets no longer get the RootAccess bucket policy statement

    When bootstrapping an S3 state bucket, Terragrunt attached a bucket policy statement with the Sid RootAccess that granted s3:* on the bucket and its objects to arn:aws:iam::<account-id>:root, an ARN that grants access to the AWS account as a whole rather than only to its root user. That statement has been removed. It widened reach to state files that routinely hold secrets, and the account already owns the bucket.

    The skip_bucket_root_access config no longer has anything to skip, and is now deprecated. Terragrunt still accepts it, and warns about it when bootstrapping a backend whose config sets it. Enable the skip-bucket-root-access strict control to turn that warning into an error.

    To grant the AWS account root user access to the state bucket, set the new enable_bucket_root_access config:

    # root.hcl
    remote_state {
    # ... other args omitted for brevity ...
    config = {
    # ... other config omitted for brevity ...
    enable_bucket_root_access = true
    }
    }
    

    Buckets that already have the statement keep it. The state backend docs cover how to remove it yourself.

    Note

    Within the 1.0 guarantees

    The 1.0 guarantees make promises about how Terragrunt remains backwards compatible. This change does not break those promises. It is listed under breaking changes so you are aware of it, in case it affects your workflows.

    The bucket policy Terragrunt writes is not part of the CLI, HCL, or output schemas the guarantees pin, and skip_bucket_root_access remains valid configuration. Removing the statement is a bug fix, and enable_bucket_root_access restores it. See Bugs in the guarantees for how a bug fix in 1.x can change your workflows.

    New Features

    Non-interactive terragrunt catalog output with --format

    The catalog TUI needs a terminal. The --format flag (env: TG_FORMAT) writes what the catalog discovers to standard output, so a script or an agent can read the catalog without one.

    --format=jsonl writes one JSON object per catalog entry, following a published JSON schema:

    terragrunt catalog --format=jsonl | jq -c '{kind, title, component_source}'
    

    --format=md writes a Markdown document with a section per entry:

    terragrunt catalog --format=md &gt; catalog.md
    

    Without --format, terragrunt catalog opens the TUI only when standard input and standard output are both terminals. Anywhere else it writes jsonl, so piping the command needs no flag:

    terragrunt catalog | jq -c '{kind, title, component_source}'
    

    Terragrunt writes each entry as it discovers it. See Non-interactive catalog for the structure of each format and how streaming behaves.

    Previously gated behind the catalog-format experiment, non-interactive catalog output no longer requires --experiment catalog-format.

    Collect runtime profiles

    Terragrunt writes CPU, heap, and goroutine profiles on request, so you can see where a slow run spends its time. Pass --profile-cpu, --profile-mem or --profile-goroutine with a path, or --profile-dir to collect all three into one directory under conventional names. Each flag has a matching TG_PROFILE_* environment variable.

    terragrunt --profile-dir /tmp/profiles run --all -- plan
    

    Read the result with go tool pprof. The profiles cover Terragrunt itself, not the OpenTofu/Terraform processes it runs.

    Previously gated behind the profiling experiment, the profile flags no longer require --experiment profiling.

    Bound discovery with --discovery-boundary and (dir) filters

    Graph filters search up to the Git repository root for dependents and follow dependencies wherever they point, so in a monorepo they can parse sibling environments a command never needed.

    A (dir) operand in a graph filter stops traversal at that directory:

    cd environments/staging
    terragrunt find --filter '(.)...vpc'
    

    From the same directory, the --discovery-boundary flag (env: TG_DISCOVERY_BOUNDARY) applies one boundary to every --filter expression on the command:

    terragrunt run --all --filter '...vpc' --discovery-boundary . -- plan
    

    Previously gated behind the bounded-discovery experiment, bounded discovery no longer requires --experiment bounded-discovery.

    Terragrunt docs MCP server

    Terragrunt now publishes the read-only Terragrunt docs MCP server, which answers Terragrunt questions from the official docs, the CLI reference, a curated design-pattern library, and real example config. The server is public and unauthenticated. Results are pinned to a Terragrunt version, and docs pages can be read at any release tag from v0.80 onward.

    For Claude Code:

    claude mcp add -s user --transport http terragrunt-docs https://mcp.docs.terragrunt.com/mcp
    

    Cursor and other MCP clients that read an mcp.json point at https://mcp.docs.terragrunt.com/mcp instead.

    The server is in public beta. It has no availability guarantee and may change significantly.

    See the install docs for the full setup.

    Download modules from OCI registries

    An oci:// source downloads a module from an OCI Distribution registry, such as Amazon ECR, GitHub Container Registry, Azure Container Registry, Google Artifact Registry, or a self-hosted one. It works in a terraform block:

    # terragrunt.hcl
    terraform {
    source = "oci://ghcr.io/acme/tofu-modules/vpc?tag=1.0.0"
    }
    

    And in the unit and stack blocks of a terragrunt.stack.hcl:

    # terragrunt.stack.hcl
    unit "vpc" {
    source = "oci://ghcr.io/acme/terragrunt-units/vpc?tag=1.0.0"
    path = "vpc"
    }
    

    Pin the artifact with tag or digest. Setting neither selects the latest tag, and a //subdir selector reaches a directory inside the module, unit, or stack. Credentials come from OpenTofu's CLI config, from ambient Docker config, and from credential helpers such as ecr-login, so one source string resolves the same way under both tofu and Terragrunt.

    Previously gated behind the oci experiment, these sources no longer require --experiment oci. See OCI registries for the publishing contract and the full authentication order.

    Track the files that OpenTofu file functions read

    Terragrunt records the files that the built-in file functions read, so reading-based filters select the units that read them without a mark_as_read call:

    • file
    • templatefile
    • fileset
    • fileexists
    • the file* hash functions, such as filesha256

    A file read from inside a template counts too.

    mark_as_read remains the way to record a file that only OpenTofu/Terraform reads, such as one passed to a module as an input, or one a run_cmd script reads.

    Generated files stored in the CAS

    The Content Addressable Store (CAS) now stores the files that generate blocks produce. Each unit's working directory gets a hard link to the stored copy, so every unit that includes this block shares one provider.tf on disk:

    # root.hcl
    generate "provider" {
    path = "provider.tf"
    if_exists = "overwrite_terragrunt"
    contents = "provider \"aws\" {}"
    }
    

    Generated files are read-only by default, so an existing hook or script that edits a generated file in place fails with a permission error. Set mutable = true on that generate block to give each unit a writable file of its own:

    generate "provider" {
    path = "provider.tf"
    if_exists = "overwrite_terragrunt"
    mutable = true
    contents = "provider \"aws\" {}"
    }
    

    Passing --no-cas turns off the CAS for a run, and Terragrunt writes generated files as plain files:

    terragrunt run --all --no-cas -- plan
    

    See Generate blocks and Immutable by default for details.

    Previously gated behind the mutable-generate experiment, CAS storage for generated files no longer requires --experiment mutable-generate.

    Iterate unit, stack, and dependency blocks with expansion

    An expansion block declares a count or a for_each, and Terragrunt reads the block it sits in once per element. This unit block generates two units, at .terragrunt-stack/aurora/web and .terragrunt-stack/aurora/api:

    # terragrunt.stack.hcl
    unit "aurora" {
    expansion {
    for_each = toset(["web", "api"])
    }
    source = "../units/app"
    path = "aurora/${each.key}"
    values = {
    role = each.key
    }
    }
    

    A stack block expands the same way, generating one stack per element.

    An expanded dependency block produces one dependency per element, and inputs reads each one by its key:

    # terragrunt.hcl
    dependency "aurora" {
    expansion {
    for_each = toset(["web", "api"])
    }
    config_path = "../aurora-${each.key}"
    }
    inputs = {
    web_id = dependency.aurora["web"].outputs.id
    }
    

    unit and stack blocks also accept an enabled attribute. Setting it to false skips the component during stack generation:

    # terragrunt.stack.hcl
    unit "canary" {
    enabled = false
    source = "../units/app"
    path = "canary"
    }
    

    Adding an expansion block to an existing block, or shrinking one, changes the addresses of the components it produces. Read the expansion reference before changing one that has already been applied.

    Previously gated behind the block-iteration experiment, expansion blocks and the enabled attribute no longer require --experiment block-iteration.

    Bootstrap, delete, and migrate Azure Storage state backends

    Terragrunt provisions the resource group, storage account, and blob container backing an azurerm state, and converges blob versioning and soft delete on both new and pre-existing accounts. It also deletes state blobs and containers, and migrates state within a storage account. Dependency outputs of Azure-backed units are read straight from the state blob, the same as S3 and GCS.

    If you already pass --backend-bootstrap, Terragrunt now creates Azure resources it skipped before.

    Previously gated behind the azure-backend experiment, these operations no longer require --experiment azure-backend. See State Backend for configuration keys and authentication.

    Set the minimum TLS version on a bootstrapped Azure storage account

    The ARM API treats an unset minimum TLS version as TLS1_0, but Azure deprecated TLS1_0 and TLS1_1 in August 2025.

    Terragrunt now provisions a new storage account with a minimum TLS version of TLS1_2. The minimum_tls_version option raises it to TLS1_3:

    remote_state {
    backend = "azurerm"
    config = {
    storage_account_name = "myterragruntstate"
    container_name = "tfstate"
    key = "${path_relative_to_include()}/tofu.tfstate"
    resource_group_name = "tofu-rg"
    use_azuread_auth = true
    minimum_tls_version = "TLS1_3"
    }
    }
    

    TLS1_2 and TLS1_3 are the only accepted values. Terragrunt rejects the deprecated TLS1_0 and TLS1_1.

    The setting applies only when Terragrunt creates the account. Terragrunt leaves an existing account's setting alone, so change it there with the Azure portal or CLI.

    Call OpenTofu 1.13 built-in functions in Terragrunt configurations

    Terragrunt evaluates the built-in functions in configurations using its own copy of the OpenTofu implementations, which tracks OpenTofu 1.13.

    These functions are now available:

    • assumeequal
    • assumelistlength
    • assumelistlengthmax
    • assumelistlengthmin
    • assumemaplength
    • assumemaplengthmax
    • assumemaplengthmin
    • assumenotnull
    • assumesetlength
    • assumesetlengthmax
    • assumesetlengthmin
    • assumestringprefix
    • base64gunzip
    • cidrcontains
    • ephemeralasnull
    • issensitive
    • templatestring
    • urldecode

    Read dependency outputs from state by default

    Terragrunt reads dependency outputs straight from the remote state object, without initializing each dependency to run tofu output or terraform output against it. This covers the S3, GCS, and Azure Storage (azurerm) backends.

    When a direct read is unsupported, such as for a backend Terragrunt has no reader for, or fails on a permissions or network error, Terragrunt falls back to tofu/terraform output -json. The outputs are the same either way, so only the speedup is lost.

    Pass --no-dependency-fetch-output-from-state (env: TG_NO_DEPENDENCY_FETCH_OUTPUT_FROM_STATE) to always load dependency outputs through tofu/terraform output -json.

    Previously gated behind the dependency-fetch-output-from-state experiment, direct state reads no longer require --experiment dependency-fetch-output-from-state. Passing --dependency-fetch-output-from-state still works, and the dependency-fetch-output-from-state strict control turns its deprecation warning into an error.

    Version constraints for registry modules

    The terraform block accepts a version attribute holding a version constraint for a tfr:// registry module. Terragrunt downloads the highest published version that satisfies the constraint, using the same syntax as the version argument on OpenTofu and Terraform module blocks:

    terraform {
    source = "tfr://registry.opentofu.org/terraform-aws-modules/vpc/aws"
    version = "~> 3.3"
    }
    

    See the terraform block reference for the full rules.

    Previously gated behind the version-attribute experiment, version constraints for registry modules no longer require --experiment version-attribute.

    S3 buckets can be created in your account regional namespace

    An account regional namespace is a reserved subdivision of the S3 bucket namespace that only your account can create buckets in, so no one else can take or re-create those names. Bucket names in it end with your account ID, the region, and -an.

    When a bucket name matches that convention, Terragrunt creates the bucket in the account regional namespace:

    # root.hcl
    remote_state {
    backend = "s3"
    config = {
    bucket = "my-tofu-state-111122223333-us-east-1-an"
    key = "${path_relative_to_include()}/tofu.tfstate"
    region = "us-east-1"
    }
    }
    

    There is no setting to enable this. S3 accepts the -an suffix only for account regional buckets, so the name alone decides. accesslogging_bucket_name is read the same way. Buckets named any other way are created in the global namespace, and the namespace is left out of the request entirely, so S3-compatible object stores are unaffected.

    A name that fits the convention but names a region other than the bucket's own fails immediately.

    Skip dependency outputs with --no-dependency-outputs

    The --no-dependency-outputs flag (env: TG_NO_DEPENDENCY_OUTPUTS) skips output resolution for every dependency block in a run, so Terragrunt does not call tofu output on dependencies that may not be applied yet:

    terragrunt run --all --no-dependency-outputs -- validate
    

    Warning

    Use this flag with commands that do not read dependency outputs, such as init and validate. While it is set, references to dependency outputs get no real value, so plan and apply can pass empty values to OpenTofu/Terraform in their place.

    Previously gated behind the optional-dependency-outputs experiment, the flag no longer requires --experiment optional-dependency-outputs.

    Skip hooks for a run with --no-hooks

    The --no-hooks flag (env: TG_NO_HOOKS) skips every hook for a run: before_hook, after_hook, and error_hook blocks.

    terragrunt run --no-hooks -- plan
    

    Previously gated behind the optional-hooks experiment, --no-hooks no longer requires --experiment optional-hooks.

    Bug Fixes

    S3 access log delivery is granted with a bucket policy

    S3 disables ACLs on new buckets by default, and a bucket with ACLs disabled rejects the ACL grant Terragrunt wrote to make an access logging bucket accept logs. AWS recommends a bucket policy over an ACL for this grant, and recommends keeping ACLs disabled in general.

    Terragrunt now creates buckets with ACLs disabled and grants access log delivery through the logging bucket's policy instead, allowing s3:PutObject for the logging.s3.amazonaws.com service principal on behalf of buckets in the same AWS account:

    remote_state {
    backend = "s3"
    config = {
    bucket = "my-state-bucket"
    key = "${path_relative_to_include()}/tofu.tfstate"
    region = "us-east-1"
    accesslogging_bucket_name = "my-logs-bucket"
    }
    }
    

    This only applies to a logging bucket Terragrunt creates. One that already exists keeps the permissions it has, whether that is the ACL grant from an earlier Terragrunt version or something you set up yourself, and Terragrunt neither reads nor writes its policy.

    skip_accesslogging_bucket_policy opts out of that grant. skip_accesslogging_bucket_acl is deprecated and now has no effect: Terragrunt puts no ACL on the logging bucket, so there is nothing left for it to skip.

    If you set skip_accesslogging_bucket_acl to work around an AccessControlListNotSupported failure on a bucket with ACLs disabled, drop it. The bucket policy covers that bucket, and the attribute now suppresses nothing. Set skip_accesslogging_bucket_policy only if you grant log delivery yourself. Terragrunt warns when the deprecated attribute is used, and the skip-accesslogging-bucket-acl strict control turns that warning into an error.

    expansion works with autoinclude and stack dependencies

    terragrunt stack generate failed with There is no variable named "each" when a unit or stack block declared both an expansion block and an autoinclude block. The error pointed at each.key in path, even when autoinclude never referenced each.

    Generation now writes an autoinclude file for each element, and each.key, each.value, and count.index inside autoinclude resolve to that element:

    # terragrunt.stack.hcl
    unit "repo" {
    source = "../units/repo"
    path = "repo"
    }
    unit "environment" {
    expansion {
    for_each = toset(["dev", "prod"])
    }
    source = "../units/environment"
    path = "environment/${each.key}"
    autoinclude {
    dependency "repo" {
    config_path = unit.repo.path
    }
    inputs = {
    environment = each.key
    repository = dependency.repo.outputs.name
    }
    }
    }
    

    The prod element gets this terragrunt.autoinclude.hcl:

    dependency "repo" {
    config_path = "../../repo"
    }
    inputs = {
    environment = "prod"
    repository = dependency.repo.outputs.name
    }
    

    A dependency block inside autoinclude that declared its own expansion block failed generation with the same error. A unit whose terragrunt.autoinclude.hcl contained one also failed to parse. An expanded unit could not be referenced from the stack file at all, since unit.<name>.path skipped it.

    Each element of an expanded unit or stack is now referenced as unit.<name>[key].path. The generated dependency block keeps its expansion block. Generation evaluates for_each or count in the stack file, where local.* and values.* are available, writes the result as a literal, and resolves config_path for each element. The generated unit expands the dependency when it is parsed:

    # terragrunt.stack.hcl
    locals {
    regions = toset(["us-east-1", "us-west-1"])
    }
    unit "vpc" {
    expansion {
    for_each = local.regions
    }
    source = "../units/vpc"
    path = "vpc/${each.key}"
    values = {
    region = each.key
    }
    }
    unit "app" {
    source = "../units/app"
    path = "app"
    autoinclude {
    dependency "vpc" {
    expansion {
    for_each = local.regions
    }
    config_path = unit.vpc[each.key].path
    mock_outputs = { vpc_id = "vpc-mock-${each.key}" }
    }
    inputs = {
    vpc_ids = { for region, vpc in dependency.vpc : region =&gt; vpc.outputs.vpc_id }
    }
    }
    }
    
    # .terragrunt-stack/app/terragrunt.autoinclude.hcl
    dependency "vpc" {
    expansion {
    for_each = toset(["us-east-1", "us-west-1"])
    }
    config_path = {
    us-east-1 = "../vpc/us-east-1"
    us-west-1 = "../vpc/us-west-1"
    }[each.key]
    mock_outputs = { vpc_id = "vpc-mock-${each.key}" }
    }
    inputs = {
    vpc_ids = { for region, vpc in dependency.vpc : region =&gt; vpc.outputs.vpc_id }
    }
    

    A stack file that declares the same unit or stack label both with and without an expansion now fails to parse, because unit.<name> cannot refer to both.

    Discovery failed the same way on a dependency whose config_path pointed at a stack directory containing an expanded unit. It dropped the dependency instead of reporting the error, so run --all did not wait for the units in that stack. The dependency now covers every element of the expanded unit.

    Malformed {} groups in glob patterns no longer crash Terragrunt

    Some glob patterns with an empty or unclosed {} group crashed Terragrunt when it matched them, e.g. terragrunt find --filter '{./a{}'. Others silently failed to match, so {}a did not match a.

    Terragrunt now refuses these patterns with an invalid pattern error. This covers filter queries, include_in_copy and exclude_from_copy in the terraform block, and .terragrunt-catalog-ignore files. A group with one empty option next to a non-empty one, such as main.tf{,.bak}, still works.

    hcl validate --inputs reads -var and -var-file arguments verbatim

    hcl validate --inputs applied shell quoting rules to each entry in extra_arguments before reading -var and -var-file from it. Those rules treat a backslash as an escape character, so on Windows a var file path such as "-var-file=${get_terragrunt_dir()}\varfiles\main.tfvars" lost its separators, and validation failed to open the file.

    Terragrunt now reads each entry in arguments exactly as written, as the single argument it becomes on the OpenTofu/Terraform command line.

    Units with identical configs each resolve their own iam_role

    When two units had the same terragrunt.hcl content, Terragrunt could assume the first unit's IAM role for both. This hit any iam_role that depends on the unit's directory, such as:

    iam_role = "arn:aws:iam::123456789012:role/${basename(get_terragrunt_dir())}"
    

    With this config in a/ and b/, b assumed role/a instead of role/b. Terragrunt now evaluates iam_role in each unit's own directory, so get_terragrunt_dir(), find_in_parent_folders(), and similar functions return that unit's paths.

    terragrunt info print --all writes JSON Lines and reports each unit's own download directory

    info print --all wrote each unit's info indented over several lines, one object after another, and gave every unit the root's download_dir:

    $ terragrunt info print --all
    {
    "config_path": "/example/live/db/terragrunt.hcl",
    "download_dir": "/example/live/.terragrunt-cache",
    "iam_role": "",
    "terraform_binary": "tofu",
    "terraform_command": "print",
    "working_dir": "/example/live/.terragrunt-cache/EfNrjc2equLKYmOZbwT2qu1dO9c/ByrgT1vMBQjFneXYgAxchposVZ0"
    }
    {
    "config_path": "/example/live/vpc/terragrunt.hcl",
    "download_dir": "/example/live/.terragrunt-cache",
    ...
    }
    

    A line-oriented reader could not take one entry at a time:

    $ terragrunt info print --all | head -1 | jq .
    jq: parse error: Unfinished JSON term at EOF at line 2, column 0
    

    The download_dir was wrong as well. run --all creates each unit's .terragrunt-cache next to that unit's configuration, so the directory reported here was not the one the unit runs against.

    With --all, Terragrunt now writes one object per line, so the output is JSON Lines, and builds each unit's context the way run --all does:

    $ terragrunt info print --all | jq -c '{config_path, download_dir}'
    {"config_path":"/example/live/db/terragrunt.hcl","download_dir":"/example/live/db/.terragrunt-cache"}
    {"config_path":"/example/live/vpc/terragrunt.hcl","download_dir":"/example/live/vpc/.terragrunt-cache"}
    

    Printing a single unit is unchanged: one indented object.

    Malformed exclude blocks report an error

    Terragrunt silently dropped an exclude block with an attribute of the wrong type, such as actions = "plan" where a list belongs, and ran the unit as if the block weren't there. The parse now fails with an error that names the file and the attribute:

    exclude block in /live/unit/terragrunt.hcl: json: cannot unmarshal string into Go struct field ExcludeConfig.actions of type []string
    

    Discovery doesn't fetch dependency outputs, so it can't evaluate an exclude block that reads one. Terragrunt still skips that block during discovery, and now logs a warning naming the file.

    Dependencies on a stack skip its disabled units

    A dependency whose config_path pointed at a stack directory also depended on the units and stacks in that stack set to enabled = false. Stack generation never writes a disabled unit, so run --all failed on the missing directory:

    You attempted to run terragrunt in a folder that does not contain a terragrunt.hcl file. Please add a terragrunt.hcl file and try again.
    

    find --dependencies and dag graph listed the same missing path as a dependency.

    The dependency now covers only enabled units. The units of a disabled stack are left out, including a tree generated before the stack was disabled.

    Stack commands respect --discovery-boundary

    stack generate, stack run, and stack output scanned the whole working directory for stack files and ignored --discovery-boundary. In a monorepo with a catalog next to live infrastructure, a catalog stack referencing files that exist only in the live tree failed the command, even though the command never asked for that stack.

    The boundary now applies to these commands, including an inline (dir) operand, and it holds when a Git expression such as [main...HEAD] generates stacks for both compared commits. This works from the repository root:

    terragrunt stack run plan --filter '(./live/)...[main...HEAD]'
    

    Terragrunt skips the catalog units outside ./live. It still scans the whole working directory when a positive filter has no dependent-side boundary and --discovery-boundary is unset, or when the boundaries fall in separate directories.

    Dependent discovery had the same gap and parsed units outside the dependent-side boundary. It now starts the search for dependents at that boundary, which can be a directory inside the working directory.

    In a Git expression, a relative boundary resolves against the repository root like any other path in the expression. Changed units outside a dependent-side boundary are ignored, and a boundary that exists in neither compared commit is an error. A dependency-side boundary only limits dependency traversal.

    --auth-provider-cmd and --queue-construct-as reject unquoted shell operators

    Terragrunt splits --auth-provider-cmd and --queue-construct-as values into words without running a shell. An unquoted shell operator such as |, ;, &&, or > used to end the value, and Terragrunt used only the words before it, so --auth-provider-cmd 'get-creds | jq .creds' ran get-creds on its own.

    A value with an unquoted shell operator is now an error. Quote the operator to pass it as part of an argument. To run a pipeline as the auth provider, put it in a script and pass the script.

    Experiments Added

    mcp-command โ€” Serve Terragrunt operations to AI agents

    The new mcp-command experiment adds the mcp command, which serves Terragrunt operations to AI agents over the Model Context Protocol.

    An agent can ask which units exist, how they depend on each other, whether configurations pass validation, what order the units run in, what a unit's applied outputs are, and more.

    Point an MCP client at the Terragrunt binary and make sure that it enables the experiment:

    // .mcp.json
    {
    "mcpServers": {
    "terragrunt": {
    "command": "terragrunt",
    "args": ["mcp"],
    "env": {
    "TG_EXPERIMENT": "mcp-command"
    }
    }
    }
    }
    

    By default, the server refuses to start any subprocess (e.g. tofu, terraform, git, or a run_cmd program). Wherever Terragrunt would have started one, the result substitutes a stand-in for its output, such as mock_outputs for a dependency output that tofu output -json would have fetched, and lists each substitution in a degraded field.

    Pass --allow=exec to let the server run the tofu, terraform, and git that Terragrunt starts on its own during a run. A program a configuration names, through run_cmd(), a before_hook, or --auth-provider-cmd, is still refused, including a tofu, terraform, or git the configuration names for itself, so pointing the server at a repository does not hand it those programs. Allow the commands you want with --allow-cmd, a pattern matched against the program and each of its arguments (e.g. --allow-cmd='jq **'), or let the read-only tools ask: when discover, render_config, validate, or run_order meets a refused program, it sends the client an elicitation naming it, which the client usually shows the person operating the agent, and runs again with whatever they accept. plan, apply, and destroy never ask, since answering would mean running them a second time.

    The remaining capabilities are denied the same way, each granted on its own:

    • --allow=http lets Terragrunt make HTTP requests on its own.

    This includes downloading a unit's remote terraform { source }, fetching a stack's sources, reaching a cloud API to assume a role or read a bucket, and reading remote state directly from blob stores.

    • --allow=sops lets it decrypt SOPS-encrypted files.

    Unless it is granted, sops_decrypt_file fails rather than handing an agent the cleartext of your secrets.

    • --allow=env passes the server's environment variables to configurations and the commands the tools run.

    Unless it is granted, tool calls start from an empty environment, so get_env() returns its default. The server also clears its own environment variables and points HOME at an empty directory, so cloud SDKs, the SOPS decrypter, and git commands Terragrunt runs will find no credentials in environment variables or your home directory.

    Granting a capability only changes the capabilities of Terragrunt. A process started under --allow=exec can still reach out on the network on its own, so tofu init will download providers whether or not --allow=http was passed to allow Terragrunt to make network requests. The directory the server is launched in is its root. A tool call targeting a directory outside it is refused, and graph traversal is bounded there too, so a filter following dependencies or dependents cannot bring back a unit from a tree the server was never pointed at. That bounds what the server acts on, not what a configuration can read: an HCL function such as file() reads the real disk wherever it points.

    You can grant multiple capabilities at once:

    // .mcp.json
    {
    "mcpServers": {
    "terragrunt": {
    "command": "terragrunt",
    "args": ["mcp", "--allow=exec", "--allow=http"],
    "env": {
    "TG_EXPERIMENT": "mcp-command"
    }
    }
    }
    }
    

    A separate flag, --dangerously-allow-apply, adds apply and destroy tools on top of --allow=exec. Without it neither tool is registered, so a client is never told they exist, and the server won't ever run apply or destroy on behalf of a client.

    With it, the tool calls return an elicitation (the protocol's way for a server to ask the client's user a question) naming the units that would be run, and the run starts only once the person operating the client accepts it. A decline ends the tool call, and a client with no way to ask anyone is refused. The approval names the units, not the changes: nothing is planned to build that list, since a plan costs a full run that the acceptance then repeats. Call the plan tool first if you want the changes in front of you before accepting.

    Only grant this capability on infrastructure you are willing to lose.

    Passing it without --allow=exec is refused at startup, since applying means running OpenTofu/Terraform. Launch the server in the environment directory you are willing to have changed rather than at the repository root, because that directory is as far as any tool call can reach:

    // .mcp.json
    {
    "mcpServers": {
    "terragrunt-throwaway": {
    "command": "terragrunt",
    "args": [
    "mcp",
    "--working-dir", "/path/to/dev",
    "--allow=exec",
    "--dangerously-allow-apply"
    ],
    "env": {
    "TG_EXPERIMENT": "mcp-command"
    }
    }
    }
    }
    

    Warning

    Agents managing infrastructure

    An agent reading your estate is still an agent acting on it. A model can be confidently wrong about what a tool does, and it can be steered by things you don't expect, including the comments in configurations, module READMEs, and command output it was pointed at. Read what an agent responds with as a proposal. Keep a person between it and anything that changes real resources, and give it credentials scoped to what you are willing to have it reach.

    The restrictions this server places on itself are not a sandbox. They bound what the tools on this server do, and nothing else. An agent that can run shell commands can run terragrunt run --all apply itself, with your ambient credentials, whether or not the server was started with --allow=exec.

    You remain responsible for how your agents manage infrastructure, and for what they do with the access you give them. An agent with these tools and your credentials can change or destroy real resources, and accepting that risk is your decision.

    The tools that run OpenTofu/Terraform use the binary named by --tf-path, and ignore a unit's terraform_binary and engine block, since Terragrunt starts both without an --allow-cmd pattern or an approval. plan, apply, and destroy take parallelism to cap how many units run at once.

    The tools the server offers, the arguments they take, and what each capability grants are documented with the mcp command.

    This will not stabilize before v1.3, so treat the tool set, the flag names, and the shape of every result as subject to change until then. The experiment documentation lists the criteria that have to be met first.

    Experiments Updated

    Eleven experiments completed

    The following experiments graduated to general availability in this release, and the features they gated are now enabled by default:

    • azure-backend
    • block-iteration
    • bounded-discovery
    • catalog-format
    • dependency-fetch-output-from-state
    • mutable-generate
    • oci
    • optional-dependency-outputs
    • optional-hooks
    • profiling
    • version-attribute

    Each feature is described in the New Features section above.

    The corresponding --experiment flags (and TG_EXPERIMENT values) are no longer needed. Passing one still works, but emits a warning about the completed experiment, so you can drop it at your convenience.

    Thank you to everyone who ran these experiments early and filed the feedback that got them here.

    tg-login โ€” Signing in to the Gruntwork Developer Portal

    The tg-login experiment now enables terragrunt login, which signs you in to the Gruntwork Developer Portal from the CLI:

    terragrunt --experiment tg-login login
    

    Once you are signed in, terragrunt catalog discovers the repositories your organization selected in the portal and adds them to your catalog. See Gruntwork Developer Portal for how to select those repositories.

    See the experiment documentation for what still has to land before it stabilizes.

    Process Updates

    Dropped the hashicorp/terraform v0.15.3 dependency

    Terragrunt drew its built-in functions from github.com/hashicorp/terraform, pinned to v0.15.3 by a replace directive. Those functions now come from Terragrunt's own copy of the OpenTofu implementations, and go.mod has no replace directives.

    Projects that import Terragrunt as a Go module no longer resolve github.com/hashicorp/terraform, and their dependency graph drops by roughly 175 modules, most of them cloud provider SDKs that Terraform used for its backends.

    Installing and running the Terragrunt binary is unchanged.

    Pull Requests

    Features

    • feat(profiling): collect runtime profiles by default by @denis256 in #6945
    • feat(dependency): read dependency outputs from state by default by @denis256 in #6932
    • feat(hcl): switch base64gzip to the current encoder by @denis256 in #6947
    • feat: Adding login command by @yhakbar in #6804
    • feat: Adding portal Catalog API client by @yhakbar in #6805
    • feat: Adding portal repositories to terragrunt catalog by @yhakbar in #6937
    • feat: Allow creation of S3 buckets in the account regional namespace by @yhakbar in #6870
    • feat: Replace bucket ACL with policy for access logging by @yhakbar in #6868
    • feat(azurerm): adapt minimum_tls_version in azure state for v1.2.0 by @denis256 in #6968
    • feat: Adding mcp command by @yhakbar in #6851

    Bug Fixes

    • fix: Ensure that worktrees get cleaned up even when cancelled by @yhakbar in #6900
    • fix: reuse assumed role session and fix dependency init check with absolute TF_DATA_DIR by @denis256 in #6902
    • fix: Adding --cas-offline support for catalog by @yhakbar in #6904
    • fix: Fixing integration of expansion with autoinclude by @yhakbar in #6936
    • fix: Patching bug with discovery not respecting enabled attribute by @yhakbar in #6939
    • fix(docs): serve the vendor tag proxies the shared GTM container needs by @ZachGoldberg in #6974
    • fix(docs): run the Google tag on the main thread and serve the container first-party by @ZachGoldberg in #6975
    • fix(docs): resolve the container's extensionless tag paths past the trailing-slash redirect by @ZachGoldberg in #6976
    • fix(docs): resolve GA4's collect endpoints past the trailing-slash redirect by @ZachGoldberg in #6978
    • fix: More URL redaction by @yhakbar in #6961
    • fix: Remove root policy in buckets by @yhakbar in #6674
    • fix: Fixing worktree optimization with Git filter expressions on Windows by @yhakbar in #6970
    • fix: correct typo in base64gzip changelog note by @denis256 in #6983
    • fix: Addressing feedback in #6851 by @yhakbar in #6991
    • fix: respect discovery boundary in stack generate and stack run by @denis256 in #6989
    • fix: guard MustWalkTerraformOutput against negative index, nil by @denis256 in #6995
    • fix: Fixing race in TestStackExpansionGitFilterSelectsRemovedInstance by @yhakbar in #7004
    • fix: Adjusts how we ensure that the CAS only gets a Git-compatible venv by @yhakbar in #7003
    • fix(strict): name base64gzip-compat experiment in legacy-base64gzip control by @denis256 in #7014
    • fix: Resolving iam_role per unit directory and erroring on malformed exclude blocks by @yhakbar in #7012
    • fix: Fixing signin display name by @yhakbar in #7015
    • fix: respect discovery boundary when parsing dependents and in Git worktrees by @denis256 in #6998

    Documentation

    • docs: clean up released version gates for v1.1.5 by @github-actions[bot] in #6905
    • docs: Bumping Bun to 1.4.2 by @yhakbar in #6951
    • docs: More stacks docs clean-up by @yhakbar in #6960
    • docs: Dropping v1 banner by @yhakbar in #6965
    • docs: Adding GW Developer Portal Private Beta callouts by @yhakbar in #6980
    • docs: clean up released version gates for v1.1.6 by @github-actions[bot] in #6985
    • docs: Adding mcp.docs.terragrunt.com install instructions by @yhakbar in #6984
    • docs: Adding docs for the login command by @yhakbar in #6996
    • docs: Adding portal catalog setup docs by @yhakbar in #6997

    CI

    • ci(coverage): suppress TestGitStoreEnsureCommit_PinnedSHAFetchesOneCommit from timing report by @denis256 in #7017

    Chores

    • chore: Enforce usage of testify by @yhakbar in #6901
    • chore: PR comments by @denis256 in #6911
    • chore: Completing block-iteration experiment by @yhakbar in #6925
    • chore: Completing oci experiment by @denis256 in #6926
    • chore: Completing bounded-discovery experiment by @yhakbar in #6928
    • chore: Completing the catalog-format experiment by @yhakbar in #6931
    • chore: Completing the mutable-generate experiment by @yhakbar in #6933
    • chore: Completing the optional-dependency-outputs experiment by @yhakbar in #6935
    • chore: Completing azure-backend experiment by @denis256 in #6929
    • chore: Cleaning up stacks docs by @yhakbar in #6950
    • chore: Completing the version-attribute experiment by @yhakbar in #6949
    • chore: Fixing TestAwsDependencyOutputOptimization tests by @yhakbar in #6953
    • chore: Vendoring OpenTofu packages by @yhakbar in #6948
    • chore: Use RustFS where AWS usage isn't strictly necessary by @yhakbar in #6954
    • chore: Fixing these test names by @yhakbar in #6955
    • chore: Parallelizing more cloud tests by @yhakbar in #6957
    • chore: Completing the optional-hooks experiment by @yhakbar in #6938
    • chore: Consolidating URL redaction by @yhakbar in #6903
    • chore: More stack parser parity tests by @yhakbar in #6941
    • chore: Adding Windows unit tests by @yhakbar in #6907
    • chore: Fix-ups from recent merge flood by @yhakbar in #6963
    • chore: fail unstaged login stub requests by @denis256 in #6964
    • chore: Fixing Windows tests by @yhakbar in #6966
    • chore: Adding better login failure messages by @yhakbar in #6962
    • chore: Renaming TestAws to TestAWS by @yhakbar in #6979
    • chore(deps): bump cloud, azure, aws, golang dependencies by @denis256 in #6973
    • chore: Fixing race in CAS tree test race by @yhakbar in #6982
    • chore: Cache in CI better by @yhakbar in #6972
    • chore: Adding full CLI fuzz by @yhakbar in #6987
    • chore: Bumping JS deps by @yhakbar in #6992
    • chore: Getting rid of gopls workflow by @yhakbar in #6994
    • chore: Streamlining CI jobs by @yhakbar in #6840
    • chore: Setting GITHUB_STEP_SUMMARY in bats tests to avoid actually writing to summaries by @yhakbar in #7000
    • chore: Bumping Go tools by @yhakbar in #6986
    • chore: Setting up per-run env and version to increase test parallelization by @yhakbar in #6827
    • chore: Adding e2e login and catalog test by @yhakbar in #6999
    • chore: Using gotestsum better by @yhakbar in #7001
    • chore: Fuzz fixes by @yhakbar in #7002
    • chore: lint fix by @yhakbar in #7011
    • chore: Drop ContextWithEnv by @yhakbar in #7010
    • chore: Using a Dev Drive in Windows CI by @yhakbar in #7013
    • chore: Adding explanations for serial tests and parallelizing some tests by @yhakbar in #6824
    • chore: Getting rid of multierror use by @yhakbar in #7016
    Original source
  • Sep 21, 2026
    • Date parsed from source:
      Sep 21, 2026
    • First seen by Releasebot:
      Sep 21, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.6

    Terragrunt fixes a Windows bug so Git filters can again find nested units, restoring find, list and browse when only nested Terragrunt configs change.

    Bug Fixes

    Git filters find nested units on Windows again

    On Windows, find, list and browse returned nothing for a Git-based filter such as --filter '[main...HEAD]' when the diff changed only unit configurations in nested directories, like nested\path\terragrunt.hcl.

    The worktree optimization in v1.1.5 checks out only the directories of changed units for these commands. On Windows, it spelled those directories with \ separators and looked them up in Git's file listing, which uses /. None matched, so it checked out nothing. Terragrunt now uses / separators for those directories on every platform.

    Original source
  • All of your release notes in one feed

    Join Releasebot and get updates from Gruntwork and hundreds of other software products.

    Create account
  • Sep 14, 2026
    • Date parsed from source:
      Sep 14, 2026
    • First seen by Releasebot:
      Sep 14, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.5

    Terragrunt ships performance boosts, sturdier caching, and bug fixes across dependency handling, generated files, and provider setup, while adding safeguards for duplicate dependency paths and new experiments for offline CAS, block iteration, and base64gzip compatibility.

    โœจ New Features

    duplicate-dependency-labels also catches a shared config_path

    Two dependency blocks with different labels can point at the same config_path. Both parse, so the same unit is declared twice, and the two blocks drift apart as soon as one gains a mock_outputs or skip_outputs the other lacks:

    dependency "vpc" {
    config_path = "../vpc"
    }
    dependency "network" {
    config_path = "../vpc"
    }
    

    Terragrunt now warns when it finds this, alongside the existing warning for two blocks sharing a label. With the duplicate-dependency-labels strict control enabled, the warning becomes an error naming both addresses and the path they share:

    /path/to/terragrunt.hcl: dependencies vpc and network both point at ../vpc; declare that dependency once and reference it under one name
    

    ๐ŸŽ๏ธ Performance Improvements

    Fewer remote probes for sources shared across units

    run --all asked the remote what a source resolved to once per unit, so a hundred units sharing one module made a hundred requests. Each of them then read the same commit out of the store for itself.

    Units that resolve the same source at the same time now share one probe, and units that need the same Git commit share the work of reading it into the CAS.

    Measured over 100 units pointing at one Git module, counting the Git commands a run spawns:

    100 units, one shared module

    Before

    After

    First run: git ls-remote

    100

    1

    First run: reading the commit into the store

    202

    4

    First run: Git commands in total

    304

    7

    Later run, source on a branch

    100

    1

    Later run, source on a version tag

    100

    0

    The last row needs the offline-cas experiment described below; the rest apply to every run. Against a local Git server the first run went from roughly 5 seconds to 0.3, and a later run from 1 second to 0.1. A real remote makes each avoided ls-remote worth more, since it costs a network round trip rather than a local process.

    The new offline-cas experiment goes a step further and has the CAS record each probe answer in the store, so a later run can skip the request. How long it trusts an answer depends on the source:

    A source pinned to a specific revision keeps its answer for 24 hours: a semantic version tag, an S3 object version, an OCI manifest digest, an exact registry module version, or a full Mercurial changeset node.

    A source that can change upstream, such as a Git branch or an OCI tag, gets a fresh probe on every run, so a push or an upload shows up immediately.

    The experiment also unlocks three flags that change how the recorded answers are used:

    • --cas-offline never contacts a remote. Sources come from the local store and the recorded answers, and anything missing is an error rather than a fetch.
    • --cas-refresh ignores the recorded answers for one run and asks every remote again.
    • --cas-probe-ttl trusts a changeable source's answer for a duration you choose, such as 10m.

    See Recorded probes and the offline-cas experiment.

    Faster first-time source downloads

    The first time Terragrunt stores a repository in the Content Addressable Store (CAS), it copies the content of every file out of the clone. It used to launch a separate git process for each one, and on repositories with many files those launches dominated the time.

    Terragrunt now reads a repository's content through a single long-lived git process, and stores several files at a time.

    In benchmarks on an Apple M3 Max:

    files

    before

    after

    change

    200

    2.16s

    0.38s

    -83%

    1,000

    10.41s

    0.74s

    -93%

    3,000

    34.25s

    1.69s

    -95%

    The saving grows with the number of files.

    This applies when the CAS does not already hold the content, such as the first use of a new module version or a run against an empty store. Downloads that the CAS can already serve skipped this work before and are unchanged.

    CAS store improvements

    The CAS no longer writes a lock file beside each object it stores. A store had one lock file for every file and every directory listing it cached, so ~/.cache/terragrunt/cas held roughly twice as many entries as the cached content needed. Lock files already written stay where they are; deleting the store while no Terragrunt process is running against it reclaims them, and the store rebuilds without them.

    Terragrunt preserves a couple of files from a repository's .git directory when it materializes a Git source, and which files those are depends on the command. Those files used to be folded into the stored entry for the commit, so the first command to fetch a commit decided what every later command received from it: a commit first cached by stack generate, which asks for none of those files, left a later run against the same commit without them. Each file is now recorded against the commit on its own, and a command receives exactly the files it asked for whether the commit was already cached or not.

    A source pinned to a full commit SHA now asks the remote for that commit alone, one commit deep, instead of fetching every branch and tag with full history. Remotes that will not serve a commit by name, such as an older or locked-down server, still get the full fetch, so pinning keeps working everywhere. Where the remote does serve it, the first fetch of a large repository transfers the pinned commit and nothing else.

    The numbers below come from micro-benchmarks run against a git server on the same machine. The fixture is a 500-commit history whose pinned commit sits 100 commits behind the tip.

    Measurement

    Before

    After

    Change

    Git objects kept after fetching the pinned commit

    543

    43

    92% fewer

    Time to fetch the pinned commit

    575ms

    482ms

    16% faster

    Against a real remote the pinned fetch saves more than the table shows, since the objects it no longer asks for would also have to cross the network.

    Faster dependents filters

    A filter with ... before its target, such as ...vpc, finds dependents by walking the directory tree around the target and parsing each configuration it passes to see whether it depends on the target. That walk parsed every configuration from scratch, even one Terragrunt had earlier in the same command, and it runs again from each dependent it finds. On a large repository, one query could read and parse the same unrelated unit once per dependent it selected.

    Terragrunt now reuses a configuration it has already parsed, so each unit is read from disk about once per query.

    In benchmarks on an Apple M3 Max, querying the dependents of a unit from its own directory, where every other unit depends on it:

    units

    before

    after

    10

    15.3ms

    10.0ms

    50

    235ms

    150ms

    200

    3.19s

    2.09s

    From the repository root, where the walk only has to rule out the units that do not depend on the target, the same query over a 1,024-unit repository went from 250ms to 131ms.

    Faster file work, especially on small CI runners

    Terragrunt frequently does a lot of small file operations at once: copying a module into its working directory, storing a repository in the Content Addressable Store (CAS), and materializing one back out. How many it ran at once scaled with the number of vCPUs seen by the Terragrunt process or the --parallelism flag if configured.

    Terragrunt now picks that number by probing the filesystem it is about to write to to guess how much throughput it can handle to improve performance.

    The gain is largest where the filesystem is much faster or slower than Terragrunt would expect, just scaling off vCPUs.

    Materializing a 3,000 file repository on a 2 vCPU runner:

    filesystem

    before

    after

    change

    ext4

    40.8ms

    24.4ms

    -40%

    btrfs

    48.7ms

    34.2ms

    -30%

    overlayfs

    71.5ms

    56.4ms

    -21%

    On a 16 vCPU machine, storing that repository for the first time is 19% faster on ext4 and 20% faster on btrfs.

    terragrunt hcl fmt now formats at most 8 files at once by default, which measured about 14% faster than one worker per CPU on a 16 core machine.

    Runs with the fast-copy strict control enabled also copy module directories faster on macOS, by around 60% in benchmarks on an Apple M3 Max.

    Faster worktrees for Git filters

    A Git-based filter, such as --filter '[main...HEAD]', generates a worktrees to be able to run tofu in states that aren't reflected in the current worktree (e.g. when a unit is deleted, Terragrunt has to run a plan -destroy or apply -destroy in the main worktree, not the HEAD worktree in the earlier example).

    As a conditional optimization, Terragrunt now reads the Git diff first and generates worktrees only when on-disk worktrees are necessary downstream.

    For commands like find, list or browse worktree generation can be skipped more aggressively, and even more performance improvements were made there.

    On a repository with 15,000 tracked files, terragrunt find --filter '[HEAD~1...HEAD]' went from 4.7s to 0.4s on an M3 Max machine.

    Lower memory use during run --all

    When you set --json-out-dir, Terragrunt saves a JSON plan for every unit it runs. It used to build each of those documents in memory in full before writing any of it to disk, so a unit with a 64 MB plan needed roughly 168 MB to save it, and every unit running in parallel needed its own. Terragrunt now writes the document as it arrives. That same plan needs about 300 KB, roughly 550x less, and saving it finishes about 18% faster.

    Two other places held on to more than they needed. During run --all plan, Terragrunt kept every unit's error output until the run finished so it could check it for a single message at the end, and it now checks that as the output streams. Responses from a provider registry were read twice on the way in, and are now read once, which uses about 19% less memory per request.

    JSON plans are also replaced atomically now. A run that fails part way through leaves the previous file in place instead of truncating it.

    mutable = true sources are cloned instead of copied

    A source marked mutable = true needs a file of its own, because a hard link would hand out the store's read-only copy. Terragrunt now asks the filesystem for a copy-on-write clone of the stored file and copies only where the filesystem has none to give. APFS, btrfs, and XFS volumes with reflink support have one.

    A cloned target shares the stored content until you write to it, so it occupies disk space only for the parts you change. On those volumes, marking a source mutable in every unit costs disk space only for what each unit edits.

    These micro-benchmarks time materializing an editable tree on APFS on an M3 Max, once copied as in earlier releases and once cloned.

    Tree

    Before (copied)

    After (cloned)

    Change

    500 files, 7.3 MiB, most around 2 KiB

    71ms

    81ms

    14% slower

    120 files, 40 MiB, 20 of them 2 MiB each

    146ms

    23ms

    84% faster

    A clone takes about the same time for a file of any size, while a copy takes longer the bigger the file. A tree of small files takes about 10ms longer to materialize, and a tree with large files materializes about six times faster.

    Terragrunt no longer records what units read unless something needs it

    Every parse used to record the files it read. Part of that record is the content of each local module a unit sources, so Terragrunt walked those module directories once per unit, on every command, whether or not anything would look at the result.

    Only four things consult the record: reading-based filter expressions, the --queue-include-units-reading flag, find --reading, and the file tree in terragrunt browse. Terragrunt now keeps it for those and skips the module walk everywhere else.

    Benchmarks on an Apple M3 Max, across 1,000 units that all source the same local module:

    files in the module

    find --dependencies

    render --all

    50

    148 ms โ†’ 135 ms

    352 ms โ†’ 259 ms

    150

    180 ms โ†’ 135 ms

    430 ms โ†’ 263 ms

    400

    268 ms โ†’ 137 ms

    631 ms โ†’ 270 ms

    render --all performs the same full parse of each unit that run --all performs before it invokes OpenTofu, so a run over units with large local modules saves comparable time before the first plan starts.

    The saving grows with the size of the local modules a repository sources, and the new times hold steady as those modules grow. Commands that do ask about reads behave as they did before.

    Finding the repository root no longer launches git

    get_repo_root(), get_path_from_repo_root(), get_path_to_repo_root(), the runner, and discovery all need the root of the enclosing repository. Terragrunt used to ask Git for it by running git rev-parse --show-toplevel, and starting that process cost far more than producing the answer did.

    Terragrunt now finds the root itself, by looking for a .git entry in the working directory and each directory above it. Linked worktrees and submodules resolve the way they did before.

    In benchmarks on an Apple M3 Max, resolving one root, where depth is how many directories separate the starting point from the root:

    depth

    before

    after

    1

    5.29ms

    14ยตs

    5

    5.20ms

    24ยตs

    10

    5.25ms

    38ยตs

    Because Terragrunt no longer asks Git, some of Git's own settings for locating a repository stop applying. GIT_CEILING_DIRECTORIES still stops the search where it did. GIT_DIR, GIT_WORK_TREE and core.worktree are ignored, and the safe.directory ownership check is not applied, so get_repo_root() now answers in a repository owned by another user where Git refuses. A path inside a bare repository still reports that there is no repository. This is assumed to be more expected from the perspective of a Terragrunt user, and usage of git rev-parse --show-toplevel from a run_cmd is still available otherwise. If this impacts your workflows, please open a bug report, and maintainers are happy to work with you on this.

    ๐Ÿ› Bug Fixes

    More generated files are written atomically

    Terragrunt used to generate most files by opening the destination and writing into it, so the file spent time on disk half-written, and a run that failed partway through left a truncated one behind.

    These now go to a temporary file that replaces the destination once it is complete:

    • Files from generate blocks
    • The config from render --write
    • Run reports from --report-file
    • The debug file from --debug
    • .terraform.lock.hcl
    • The CLI config Terragrunt generates for OpenTofu/Terraform when the Provider Cache Server is enabled

    backend commands no longer fail on unapplied dependencies

    backend bootstrap, backend migrate and backend delete used to read the whole configuration of every unit they touched, which meant fetching the outputs of every dependency block. Declaring a dependency on a unit you had not applied yet was enough to stop them with the "detected no outputs" error, even when nothing in remote_state read that dependency.

    These commands now read only the remote_state block and the terraform block's source. They never fetch dependency outputs. A remote_state that does read a dependency output still resolves it, and still reports missing outputs when the dependency has not been applied.

    base64gzip() returns the v1.1.3 bytes again

    Terragrunt v1.1.4 was built with Go 1.27, which changed the compressed bytes produced by base64gzip(). The bytes decompress to the same content, but a resource that compares the encoded value, such as an EC2 instance with user_data_base64 and user_data_replace_on_change = true, planned a replacement after the upgrade.

    base64gzip() now returns the bytes it returned in v1.1.3 and earlier, so upgrading plans no change. Terragrunt warns once per run that this is legacy behavior. If you already applied the v1.1.4 output, every plan shows the encoded value changing back until you apply it or enable the strict control below, and a resource that depends on stability of base64gzip bytes is replaced by that apply.

    Terragrunt 1.2 will switch base64gzip() to the new encoder by default. The new base64gzip_compat() function, behind the base64gzip-compat experiment, returns the v1.1.3 bytes permanently (assuming the experiment eventually stabilizes), so call it where the encoded value must stay stable across upgrades. This function may be removed in a future release.

    To keep the current Go encoder's output now and silence the warning, enable the new legacy-base64gzip strict control:

    terragrunt run plan --strict-control legacy-base64gzip
    

    Deleted files in the CAS are fetched again instead of failing the run

    When something removes a file from the Content Addressable Store (CAS) that a cached source still needs, Terragrunt now downloads that source again and restores what is missing, then carries on.

    Terragrunt used to treat a cached source as complete once it had been downloaded, so a file deleted from the store afterwards ended the run with a read failure naming a path inside the store. Recovering meant clearing the store by hand.

    A source that no longer supplies the missing content still fails, and now says which object the store is missing. The same is true of a cas:: reference in a stack file, which names stored content directly and has no source behind it to download again, and of a run under --cas-offline, which forbids the download that would restore the store.

    catalog sanitizes the content it draws from a repository

    terragrunt catalog browses repositories you point it at, and draws their titles, descriptions, tags and READMEs to the terminal as it finds them. The catalog command did not appropriately sanitize content from repositories to ensure that the content rendered correctly in terminals.

    catalog now sanitizes everything it draws, the way terragrunt browse already sanitized the files it previews. Control characters become the Unicode replacement character, so that content draws as visible placeholders. --format jsonl and --format md keep the text as the repository wrote it.

    Fixed a crash in terragrunt catalog when a repository cannot be reached

    terragrunt catalog now reports the underlying git error when it cannot reach a repository listed in the catalog block. Previously, this could cause a crash part-way through loading. This affected any repository Terragrunt could not clone, e.g. an SSH URL with no usable key, a private repository without credentials, or a remote that timed out.

    find --dependencies lists dependencies in a stable order

    When a unit had more than one dependency, terragrunt find --dependencies --json could report them in a different order on each run, with no change to the configuration.

    The order is now fixed. list, dag graph, and browse sorted before rendering already, so their output is unchanged.

    Support backend assume_role during direct dependency state reads

    With dependency-fetch-output-from-state enabled, direct S3 state reads now correctly chain the backend's assume_role onto the dependency's execution role. Previously, cross-account dependency state reads failed with 403 AccessDenied when the remote_state block configured a separate assume_role for state access.

    Dependency state read failures fall back

    With the dependency-fetch-output-from-state experiment enabled, network, permissions, and parsing failures from a direct dependency state read could end a run that worked through native output retrieval.

    Outside render and render-json, Terragrunt now retries failed direct reads with tofu output or terraform output. If native output retrieval succeeds, the run continues and only the direct-read speedup is lost. Missing state and the two render commands retain their existing mock-output behavior.

    This fallback also covers OpenTofu client-side state encryption. Terragrunt recognizes the encrypted envelope and retries output retrieval through the configured binary instead of treating the dependency as having no outputs. If that binary can decrypt the state and native output retrieval succeeds, only the speedup is lost. render and render-json still require --no-dependency-fetch-output-from-state when they must resolve real outputs from encrypted state.

    Current flag names take precedence over deprecated ones

    A setting given under both its current name and a deprecated one took the deprecated value whatever the source of each, so TERRAGRUNT_LOG_LEVEL=debug in the environment overrode TG_LOG_LEVEL=info set beside it.

    A command-line argument now beats an environment variable under either name, and at the same level the current name beats the deprecated one. A --terragrunt-* argument still overrides a TG_* variable from the environment, so a script mixing the two keeps working.

    exec accepts --source, --source-map, and --no-auto-init

    terragrunt exec rejected --source, --source-map, and --no-auto-init as invalid flags, one message per flag: flag --source-map is not a valid flag for exec . It reads configuration and downloads source the same way run does, so there was no way to point exec at a local copy of a module, or to stop it from running init. All three flags are now registered on exec.

    terragrunt exec --source-map git::ssh://[email protected]/acme/modules.git=/local/modules -- tfmigrate plan
    

    exec therefore also reads TG_SOURCE, TG_SOURCE_MAP, and TG_NO_AUTO_INIT, along with the deprecated TERRAGRUNT_SOURCE, TERRAGRUNT_SOURCE_MAP, and TERRAGRUNT_AUTO_INIT, which it previously ignored. If you export any of those for run, exec starts honoring them too.

    --no-auto-init reaches the unit exec targets only under --in-download-dir, since exec otherwise never runs init for it. It also reaches units named in dependency blocks, with or without that flag, because Terragrunt initializes a dependency when resolving its outputs requires it.

    Direct GCS state reads work with Workload Identity Federation

    With the dependency-fetch-output-from-state experiment enabled, a GCS backend authenticated through Workload Identity Federation still ran tofu output or terraform output for every dependency, so the experiment made no difference.

    It affected any credentials file of type external_account, which is what google-github-actions/auth writes and points GOOGLE_APPLICATION_CREDENTIALS at. Terragrunt read only service_account and authorized_user files directly.

    Terragrunt now reads external_account credentials files directly, including the service-account impersonation that google-github-actions/auth configures when you give it a service account. A direct read requires the file's credential_source to be one of:

    • url
    • file with an absolute path

    Any other credential_source keeps the previous behavior, and the dependency still runs tofu output or terraform output. Reading those directly would use Terragrunt's own process rather than the unit's environment to resolve the identity:

    • executable would run the command with Terragrunt's environment.
    • AWS (an environment_id such as aws1) would use Terragrunt's AWS credentials.
    • file with a relative path would resolve against Terragrunt's working directory.

    The impersonate_service_account backend setting is a separate feature and is not affected. Backends that set it still run tofu output or terraform output.

    Files from generate blocks are created as 0600

    A generate block writes files for Terragrunt and the processes it spawns, all of which run as the user who ran Terragrunt. Creating them as 0644 granted read access that nothing uses.

    They are now created as 0600. Under the mutable-generate experiment, a block without mutable = true gets a read-only link to a copy shared between working directories, and Terragrunt stores new content as 0400 rather than 0444. With mutable = true, the block keeps a writable 0600 file of its own.

    Content the CAS is already holding keeps the permissions it was stored with, since changing them would change every file linked to that copy. Those files stay 0444 until the cache is cleared. Run with --log-level debug to see which ones.

    EC2 instance role credentials work again from inside a container

    Since v1.1.4, Terragrunt running in a container on an EC2 instance could fail to use the instance's IAM role when the instance metadata service has a hop limit of 1. Runs failed with:

    error assuming role: operation error STS: AssumeRole, get identity: get credentials:
    failed to refresh cached credentials, no EC2 IMDS role found,
    operation error ec2imds: GetMetadata, canceled, context deadline exceeded
    

    In that setup the IMDSv2 token request never gets an answer. Terragrunt v1.1.4 waited on it until the whole credential lookup timed out, so the IMDSv1 fallback that v1.1.3 and earlier relied on never ran.

    Terragrunt now gives up on the IMDSv2 token request quickly and falls back to IMDSv1, as it did before v1.1.4. No configuration change is needed.

    IAM role credentials are reused for --json-out-dir plan export

    After v1.1.4, run --all plan with an IAM role and --json-out-dir could make a second sts:AssumeRole request. That request used the role session itself and failed with AccessDenied unless the role trusted itself.

    Terragrunt now caches the assumed session in-process until five minutes before it expires (default session length is one hour when --iam-assume-role-duration is unset), keyed by role configuration and source identity, so the JSON export reuses the first assumption. Setting --iam-assume-role-duration was already a working workaround and remains supported.

    If a session cannot be refreshed but has not yet expired, Terragrunt logs a warning and continues with the cached credentials rather than failing the run.

    Provider Cache Server reads only the running implementation's CLI config files

    In v1.1.4, the Provider Cache Server started reading OpenTofu's CLI config file locations (~/.tofurc and $XDG_CONFIG_HOME/opentofu/tofurc) regardless of which binary Terragrunt was running. A machine with a stray ~/.tofurc (for example, one declaring a network_mirror) could break terragrunt init for Terraform users with errors like:

    ERROR Failed to get provider versions from "network_mirror '...'": invalid character '&lt;' looking for beginning of value
    

    Terragrunt now detects whether the configured binary is OpenTofu or Terraform before starting the cache server and reads only that implementation's CLI config files:

    • OpenTofu reads the first of these that exists: ~/.tofurc, ~/.terraformrc, $XDG_CONFIG_HOME/opentofu/tofurc (on Windows: %APPDATA%\tofu.rc, then %APPDATA%\terraform.rc).
    • Terraform reads only ~/.terraformrc (%APPDATA%\terraform.rc on Windows).

    For both implementations, Terragrunt also merges the *.tfrc and *.tfrc.json fragments from the CLI config directory: ~/.terraform.d (%APPDATA%\terraform.d on Windows), or $XDG_CONFIG_HOME/opentofu for OpenTofu when ~/.terraform.d does not exist.

    Setting TF_CLI_CONFIG_FILE continues to override the config file location for both implementations.

    The same selection applies to the credentials read for module registry downloads and version-constraint resolution, kept separately per implementation within a single run.

    The implementation is detected from the binary Terragrunt is configured to run at startup (--tf-path, TG_TF_PATH, or the first of tofu/terraform found on PATH); a terraform_binary setting inside a unit's configuration does not change which files the cache server reads. When a run's implementation differs from the one the cache server was configured for, and the two implementations would read different CLI config files on that machine, that run skips the provider cache and uses its own CLI configuration, and Terragrunt prints a warning when such a run initializes providers (init or providers lock). Both implementations resolve to the same files when none of the implementation-specific files above exist, or when TF_CLI_CONFIG_FILE names the file. Every run then uses the cache whichever binary it runs. If detection fails, Terragrunt falls back to OpenTofu's file locations.

    Run report includes cause for units that fail before OpenTofu/Terraform

    Units that failed during config evaluation or dependency output resolution were reported as a run error with an empty cause. The report now records the underlying error text in Cause.

    Thanks to @Tensho for contributing this fix!

    Fixed S3-compatible source downloads with environment credentials

    Downloading unit sources from S3-compatible services (s3::https://minio.example.com/...) now works when credentials are supplied via environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) or IAM roles rather than embedded in the URL query string. Previously, the custom endpoint was only pinned when credentials were present in the URL, causing the AWS SDK to redirect requests to amazonaws.com and fail with InvalidAccessKeyId.

    Stack dependencies include outputs from nested stacks

    A dependency pointing at a directory that holds a terragrunt.stack.hcl now reads the outputs of units generated by that stack's nested stack blocks. The run queue already waited for those units, but their outputs were missing from the dependency.

    Each nested stack adds a level named after it, the same address terragrunt stack output gives those units:

    dependency "network" {
    config_path = "../network"
    }
    inputs = {
    vpc_id = dependency.network.outputs.vpc.vpc_id
    subnet_id = dependency.network.outputs.subnets.subnet.subnet_id
    }
    

    mock_outputs entries for a nested stack's units nest under the stack's name the same way.

    A unit and a nested stack with the same name in one stack file share an address, so a dependency on that stack now errors once both have outputs to read. terragrunt stack output already rejects the same configuration. Rename one of the two blocks to give each its own address.

    ๐Ÿงช Experiments Added

    base64gzip-compat experiment adds a base64gzip_compat HCL function

    Enable the new base64gzip-compat experiment to use the base64gzip_compat(str) HCL function.

    base64gzip_compat returns the value base64gzip returned in Terragrunt v1.1.3 and earlier, and keeps returning it after Terragrunt 1.2 switches base64gzip() to the current Go encoder. Use it where the encoded value must stay stable across upgrades:

    inputs = {
    user_data_base64 = base64gzip_compat(file("${get_terragrunt_dir()}/user-data.sh"))
    }
    

    Calling base64gzip_compat without enabling the base64gzip-compat experiment returns an error. The name may still change to match OpenTofu.

    offline-cas gates the CAS probe cache

    The offline-cas experiment has been added as the gate for the probe cache the CAS keeps, in which it records what each source resolved to so a later run can skip asking the remote.

    Enabling the experiment turns the cache on and unlocks three flags that change how its answers are used: --cas-offline, --cas-refresh, and --cas-probe-ttl. Setting one of them without the experiment returns an error naming the flag.

    terragrunt run --experiment offline-cas --all --cas-offline -- plan
    

    Without the experiment nothing is recorded or served, and every run probes every source, as before.

    See the experiment documentation for what each flag does and what has to land before it stabilizes.

    tg-login reserved for signing in to the Gruntwork Developer Portal

    The tg-login experiment has been added as the gate for terragrunt login, a command for signing in to the Gruntwork Developer Portal. Once it lands, signing in lets terragrunt catalog read the repositories your organization selected in the portal rather than a catalog block you maintain yourself.

    In this release the flag is reserved only. Enabling it has no effect, and no command reads it.

    See the experiment documentation for what is planned and what has to land before it stabilizes.

    ๐Ÿงช Experiments Updated

    azure-backend can assign the blob data role during bootstrap

    Creating an Azure storage account grants no access to the blobs inside it, so an identity using use_azuread_auth could bootstrap the backend and then fail to read state as unauthorized until someone granted the data-plane role by hand.

    With the azure-backend experiment enabled, assign_blob_data_role = true now has bootstrap grant Storage Blob Data Contributor on the storage account:

    remote_state {
    backend = "azurerm"
    config = {
    storage_account_name = "myterragruntstate"
    container_name = "tfstate"
    key = "${path_relative_to_include()}/terraform.tfstate"
    resource_group_name = "terraform-rg"
    use_azuread_auth = true
    assign_blob_data_role = true
    }
    }
    

    The role goes to the identity Terragrunt authenticated as, resolved from the access token it already holds rather than from a directory lookup, so it works for identities that cannot read Microsoft Entra. Set principal_id to grant the role to a different user, group, or service principal.

    Existing assignments are detected and left alone, so reruns need only read permission on role assignments.

    The setting is opt-in: creating a role assignment requires Microsoft.Authorization/roleAssignments/write, which Contributor does not include. Leaving it unset preserves the previous behavior of assigning nothing.

    expansion blocks now iterate dependency, unit, and stack blocks

    With the block-iteration experiment enabled, a dependency, unit, or stack block can have an expansion block declaring a count or a for_each. Terragrunt reads the block once per element, producing one dependency, unit, or stack for each:

    # terragrunt.stack.hcl
    unit "aurora" {
    expansion {
    for_each = toset(["web", "api"])
    }
    source = "../units/app"
    path = "aurora/${each.key}"
    values = {
    role = each.key
    }
    }
    

    You address each element by its key. An expanded dependency is read as dependency.aurora["web"].outputs.id, and terragrunt stack output 'aurora["web"].role' reaches one element of an expanded unit.

    Adding an expansion to a block that did not have one therefore changes its address, and shrinking a for_each or lowering a count removes addresses. Terragrunt has no moved equivalent, so nothing records the rename for you: references and stack output scripts need updating by hand, and state left behind at an address that no longer exists has to be destroyed deliberately.

    The experiment also enables an enabled attribute on unit and stack blocks. Setting it to false drops the component from stack generation and from terragrunt stack output, and leaves every other address alone. dependency blocks accept enabled without the experiment.

    See the expansion block reference for the rules, the addressing scheme, and how to clean up state left behind when an expansion shrinks.

    symlinks experiment: include_in_copy copies the contents of symlinked directories again

    In v1.1.4, files behind a symlinked directory named in include_in_copy were not copied into the OpenTofu/Terraform working directory, so they were missing from .terragrunt-cache. exclude_from_copy patterns reaching through a symlinked directory also excluded nothing.

    With the symlinks experiment enabled (--experiment symlinks or TG_EXPERIMENT=symlinks), patterns rooted at a symlinked directory expand through the link again, for both include_in_copy and exclude_from_copy, as in v1.1.3 and earlier. Without the experiment, the v1.1.4 behavior is unchanged.

    A link that points back at a directory already being copied, or at a parent of one, such as a link to the unit directory itself, is skipped. Terragrunt logs a warning naming the link when that happens.

    render previews an expanded dependency block written in JSON

    With the block-iteration experiment enabled, a configuration written in JSON now renders the same way an HCL one does. It has no HCL to quote, so Terragrunt writes the block as the HCL that means the same thing and previews the elements underneath it:

    $ cat terragrunt.hcl.json
    {"dependency": {"shard": {
    "expansion": {"count": 2},
    "config_path": "../shard-${count.index}"
    }}}
    $ terragrunt render --experiment block-iteration
    dependency "shard" {
    expansion {
    count = 2
    }
    config_path = "../shard-${count.index}"
    }
    # Expands to:
    #
    # dependency "shard" {
    # config_path = "../shard-0"
    # }
    #
    # dependency "shard" {
    # config_path = "../shard-1"
    # }
    

    Previously the elements rendered as ordinary blocks, which repeated one label. Terragrunt warns about that and rejects it under the duplicate-dependency-labels strict control, so the rendered file did not read back.

    --format json no longer drops the elements either. Its dependency map is keyed by label, which every element shares, so it kept whichever element came last. JSON has no comment to preview the elements in, so it now emits the block as it was written, references and all:

    $ terragrunt render --format json --experiment block-iteration
    {
    "dependency": {
    "shard": {
    "expansion": { "count": 2 },
    "config_path": "../shard-${count.index}",
    "skip_outputs": true
    }
    }
    }
    

    Whichever syntax you write and whichever format you ask for, rendering the output again returns it unchanged.

    Expanded units keep their own outputs when a whole stack is a dependency

    With the block-iteration experiment enabled, a dependency pointing at a directory that holds a terragrunt.stack.hcl collected the outputs of an expanded unit under the block's bare label. Every element wrote to that one label, so only the last one survived, and reading it returned another element's outputs.

    Each element is now reachable under its own key, matching the address terragrunt stack output already gives it:

    dependency "networking" {
    config_path = "../live"
    }
    inputs = {
    web_id = dependency.networking.outputs.aurora["web"].id
    api_id = dependency.networking.outputs.aurora["api"].id
    }
    

    A unit that declares no expansion is still read as dependency.networking.outputs.vpc.id.

    Pull Requests

    โœจ Features

    • feat: Adding tg-login experiment by @yhakbar in #6759
    • feat(azure): assign blob data role on bootstrap in Azure by @denis256 in #6764
    • feat: Add ability to open browser for approval by @yhakbar in #6793
    • feat: Adding CLI poll for login approval by @yhakbar in #6795
    • feat: Adding token storage by @yhakbar in #6801

    ๐Ÿ› Bug Fixes

    • fix(cliconfig): isolate cloned helpers and hosts by @denis256 in #6782
    • fix: Check duplicate dependency config path by @yhakbar in #6834
    • fix: Writing more generated files atomically by @yhakbar in #6776
    • fix(config): harden dependency state output fetching by @denis256 in #6794
    • fix(provider-cache): Fix provider cache configuration selection by @denis256 in #6789
    • fix: Sanitizing some untrusted input by @yhakbar in #6802
    • fix: restore AWS IMDS credential fallback for container environments by @denis256 in #6837
    • fix(gcs): read dependency outputs from state with external_account creds by @denis256 in #6815
    • fix: Expand include_in_copy/exclude_from_copy globs through symlinked directories by @denis256 in #6817
    • fix: Fixing stack dependency unit output cty access by @yhakbar in #6839
    • fix: adding --source-map and --no-auto-init to terragrunt exec by @denis256 in #6792
    • fix(config): restore v1.1.3 base64gzip output by default by @denis256 in #6835
    • fix(getter): pin S3-compatible endpoint independently of URL credentials by @denis256 in #6857
    • fix: Fixing catalog nil logger panic by @yhakbar in #6871
    • fix: Fixing render --json expansion logic by @yhakbar in #6763
    • fix: populate run report cause for units that fail before tofu/terraform runs by @Tensho in #6819
    • fix: Use partial parse when possible for backend commands by @yhakbar in #6790
    • fix(config): use backend assume_role for direct dependency state reads by @denis256 in #6883
    • fix: Preventing Windows flag usage panic in tests by @yhakbar in #6877
    • fix: Adding store repair for corrupted CAS stores by @yhakbar in #6872
    • fix: Fixing accessing nested stack outputs in stack dependencies by @yhakbar in #6892
    • fix: Fixing mutable-generate tmp file leak by @yhakbar in #6895
    • fix(amazonsts): stop json-out-dir IAM self-assume by @denis256 in #6899

    ๐ŸŽ๏ธ Performance

    • perf: Pre-compile color table with a generate script by @yhakbar in #6780
    • perf: Use git cat-file --batch instead of multiple git cat-file calls per blob by @yhakbar in #6838
    • perf: Tuning FS concurrency by @yhakbar in #6854
    • perf: Inline git rev-parse --show-toplevel as a Go func by @yhakbar in #6867
    • perf: Streaming buffered output by @yhakbar in #6761
    • perf: Using singleflight cached CAS probe by @yhakbar in #6841
    • perf: Using a sync.Pool for hot buffers by @yhakbar in #6769
    • perf: Reuse already-parsed configs when discovering dependents by @yhakbar in #6849
    • perf: Improving worktree performance by @yhakbar in #6864
    • perf: Avoid tracking reading when unnecessary by @yhakbar in #6862
    • perf: Improve CAS store hygiene by @yhakbar in #6843

    ๐Ÿ“– Documentation

    • docs: Bumping Bun to v1.4 by @yhakbar in #6762
    • docs: Updating docs for supported flags on dag graph by @yhakbar in #6788
    • docs: Updating block-iteration documentation by @yhakbar in #6844
    • docs(changelog): note report Cause for prerun failures by @denis256 in #6878
    • docs: Adding sign-commits to the use of peter-evans/create-pull-request for docs clean-up PRs by @yhakbar in #6891
    • docs: Closing gaps in recent feature docs by @yhakbar in #6893
    • docs: Addressing feedback from #6892 by @yhakbar in #6896
    • docs: Fixing docs build by @yhakbar in #6897

    ๐Ÿงน Chores

    • chore: Adding exec sandboxed logic to seatbelt by @yhakbar in #6760
    • chore: Collapse runner packages by @yhakbar in #6765
    • chore: Running go fix ./... on all tags by @yhakbar in #6766
    • chore: dependencies update by @denis256 in #6719
    • chore: Validating device auth duration better by @yhakbar in #6779
    • chore: Moving integration tests to in-memory CLI tests by @yhakbar in #6771
    • chore: Moving discovery tests in-memory by @yhakbar in #6774
    • chore: Typed stack validation errors and consistent generated-path comparison by @yhakbar in #6777
    • chore: Adding injectable cap to speed up TestUnitPathsFromStackDir_DepthCapReturnsError test by @yhakbar in #6781
    • chore: Modernizing with new Go 1.27 constructs by @yhakbar in #6772
    • chore: Fixing lints on main by @yhakbar in #6785
    • chore: go mod fixes by @denis256 in #6786
    • chore: Deterministic dependency order for find --dependencies --json by @yhakbar in #6800
    • chore(deps): bump google.golang.org/grpc from 1.83.0 to 1.83.1 by @dependabot[bot] in #6808
    • chore(deps): bump the js-dependencies group across 1 directory with 11 updates by @dependabot[bot] in #6806
    • chore(deps): bump go dependencies by @denis256 in #6820
    • chore: Adding test coverage for filters with block iteration by @yhakbar in #6830
    • chore: Improving expansion diagnostics by @yhakbar in #6833
    • chore: Cover object and tuple for_each in the expansion engine tests by @yhakbar in #6829
    • chore: Enabling nolintlint by @yhakbar in #6803
    • chore: Adding explicit capacity hints by @yhakbar in #6825
    • chore: Adding full block-iteration lifecycle tests by @yhakbar in #6846
    • chore: Addressing review feedback from #6854 by @yhakbar in #6865
    • chore: Fixing cloud credentials test env semantics by @yhakbar in #6826
    • chore: Fixing sandbox tests by @yhakbar in #6882
    • chore(deps): bump astro from 7.2.7 to 7.2.8 in /docs by @dependabot[bot] in #6855
    • chore(deps): bump the js-dependencies group across 1 directory with 8 updates by @dependabot[bot] in #6885
    • chore: Clean-up partial worktrees on failure by @yhakbar in #6884
    • chore: Bumping go-runewidth to v0.0.30 by @yhakbar in #6879
    • chore: Bumping go-getter to v2.2.4 by @yhakbar in #6881
    • chore: Generating .terraform directory on-demand instead of leaving it committed in the repo by @yhakbar in #6887
    • chore: Globally replacing xsync.Map with a map and mutex by @yhakbar in #6880
    • chore: General clean-up from recent PRs by @yhakbar in #6886
    • chore: Revert to materializing worktrees with git checkout instead of git archive by @yhakbar in #6890
    Original source
  • Aug 27, 2026
    • Date parsed from source:
      Aug 27, 2026
    • First seen by Releasebot:
      Aug 28, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.4

    Terragrunt ships interactive scaffold prompts, stricter dependency label checks, faster startup, and a wide set of fixes across CAS, provider caching, rendering, HCL validation, and security hardening. It also adds experiment updates for direct state reads and dependency expansion previews.

    โœจ New Features

    duplicate-dependency-labels strict control

    Declaring two dependency blocks with the same label in one terragrunt.hcl configuration file parsed without error, and then quietly resolved every reference to that label to whichever block came last. The blocks before it were silently overridden:

    dependency "vpc" {
    config_path = "../vpc-us-east-1"
    }
    dependency "vpc" {
    config_path = "../vpc-us-west-2"
    }
    inputs = {
    # Reads ../vpc-us-west-2.
    vpc_id = dependency.vpc.outputs.vpc_id
    }
    

    Terragrunt now warns when it finds this. With the new duplicate-dependency-labels strict control enabled, the warning becomes an error naming the address the blocks share:

    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
    

    Give each block a label of its own. A configuration that was relying on the shadowing to pick the last block should keep only that block.

    scaffold asks for values interactively

    Scaffolding from the command line wrote # TODO placeholders for every input and left you to fill them in by hand, while scaffolding the same component from the Catalog TUI opened a form and collected them. terragrunt scaffold now opens that same form:

    terragrunt scaffold github.com/gruntwork-io/terragrunt-infrastructure-modules-example//modules/mysql
    

    For a module or a template it lists the source's variables; for a unit or a stack it lists the values.* references its configuration makes, which are written to terragrunt.values.hcl. Dismissing the form with esc writes nothing.

    The form is skipped, and the placeholders written as before, when you pass --non-interactive, when stdin is not a terminal, or when the source asks for nothing. A scaffold in a CI job, or one run by another program, therefore behaves exactly as it did.

    See Scaffold for the full behavior, and the form's keybindings for driving it.

    ๐ŸŽ๏ธ Performance Improvements

    Faster startup when --tf-path is not set

    When you don't set --tf-path, Terragrunt picks the binary it wraps by looking for tofu on your PATH and falling back to terraform when it isn't there. Terragrunt used to make that choice by running tofu -version, which meant launching a process at the start of every command, including commands like find and list that never run the binary. That process launch is gone, and a terragrunt --version benchmark runs roughly 1.7x faster as a result.

    This changes what happens when tofu is on your PATH but can't run: Terragrunt now selects it and reports the failure rather than silently falling back to terraform. Set --tf-path or TG_TF_PATH to pick the binary yourself.

    ๐Ÿ› Bug Fixes

    Autoinclude dependency overrides no longer evaluate replaced paths

    Terragrunt used to evaluate a dependency's original config_path before applying a sibling autoinclude override. This could prevent a unit from being parsed when the original path referenced a value that the unit no longer supplied, even though the autoinclude replaced that path. Terragrunt now leaves replaced dependency blocks undecoded, then applies the autoinclude override. Dependency blocks without an autoinclude override are still validated.

    Blocks that use expansion are still decoded, because a bare autoinclude label does not name their instances. If the autoinclude also declares the same label without expansion, Terragrunt reports a dependency label collision.

    Fixed Git sources with a depth query parameter

    A terraform.source (or stack source) URL carrying the go-getter depth query parameter, such as ...vpc.git?depth=1&ref=v5.21.0, failed to download since v1.1.0, when the CAS became the default path for Git sources. Terragrunt lifted ref out of the URL but left depth in place, so git received ...vpc.git?depth=1 and rejected it as an invalid repository name. A URL with depth and no ref hit the same failure.

    Terragrunt now strips depth, with or without a ref, before invoking git, so these sources download again. The clone depth itself always comes from --cas-clone-depth, which defaults to 1; a depth on a source URL is never applied for CAS clones.

    CAS handles local sources that have already been initialized

    With CAS enabled, reading a local source that had already been initialized failed and fell back to the slower standard copy. Generating a stack from such a unit logged CAS processing failed ... source escapes repository root.

    Provider caching was the cause. Both the Provider Cache Server and the Automatic Provider Cache Dir leave the plugins under .terraform pointing into a shared cache outside the source. CAS read those links as the source reaching outside itself and refused to copy the link for safety.

    CAS now leaves .terraform and .terragrunt-cache out of local sources, keeping .terraform.lock.hcl and everything else. OpenTofu, Terraform, and Terragrunt rebuild both directories on demand, so units and stacks no longer receive a stale copy of either. Running tofu init in a source directory no longer changes that source's CAS key.

    Fixed the signal sent to a running command during shutdown

    On Windows, when a failure rather than Ctrl+C cancelled a run, Terragrunt crashed with a nil pointer panic instead of stopping the command it had started. It now terminates the command, which is the closest thing Windows offers to an interrupt.

    On every platform, when a command exited on its own during the grace period after Ctrl+C, Terragrunt could still send it the signal and then log a forwarding error against a process that was already gone.

    terraform_binary respected when reading dependency outputs

    Reading a dependency block's outputs ignored the terraform_binary of the unit being read and fell back to the auto-detected binary, which is OpenTofu whenever tofu is on your PATH. With terraform_binary = "terraform", a unit ran through Terraform while the dependency it consumed was read through OpenTofu. A run --all over units that each worked on their own then failed with a backend initialization error, followed by a misleading There is no variable named "dependency".

    Dependency outputs are now read through the binary the dependency itself configures, so a unit's terraform_binary applies wherever its state is read. --tf-path and TG_TF_PATH still take precedence over the config value.

    Numbers with extreme exponents fail fast instead of stalling

    A number literal such as 9E9999999 in inputs, locals, or a dependency block's mock_outputs used to cost over a minute of CPU on a single unit. Written out in decimal that number is ten million digits long, and terragrunt render --format=json produced every digit before failing with a ten megabyte error message.

    Terragrunt now rejects numbers larger than 1e4096, and non-zero numbers smaller than 1e-4096, before it tries to write them out, and names the attribute holding the value:

    count: number is outside the supported range of 1e-4096 to 1e4096
    

    Numbers inside that range are unaffected.

    Registry credentials are no longer copied into the generated CLI config

    When the Provider Cache Server is enabled, Terragrunt writes a CLI config for OpenTofu/Terraform into each unit's working directory, based on your own CLI config. That generated file used to include a copy of every credentials block from your config, including the ones for registries Terragrunt routes through the cache server.

    Those copies were never read. For a routed registry, Terragrunt sets the matching TF_TOKEN_<hostname> environment variable, which takes precedence over a credentials block, and the cache server presents your real credentials when it contacts the registry on your behalf. The generated file now leaves the block out for those registries, so your token stays in the CLI config you put it in instead of being duplicated somewhere it had no effect.

    Credentials for hosts the cache server does not route are unchanged, since OpenTofu/Terraform contacts those directly and still reads them from the generated config.

    Upgrading does not rewrite the files an earlier version already generated. Each is named .terraformrc and sits in a unit's working directory, which is under .terragrunt-cache for remote sources. Delete those files, or clear the cache, to get the copied credentials off disk.

    Generated files are readable only by the user who ran Terragrunt

    Terragrunt created several files and directories that other users on the same machine could read:

    The CLI config Terragrunt writes for OpenTofu/Terraform when the Provider Cache Server is enabled, and the directory holding it.
    The JSON plan files written to --json-out-dir, and that directory.
    The directories holding the plan files written to --out-dir.
    The config written by render --write, which holds the resolved values of inputs, locals, and dependency outputs.

    Terragrunt now creates those files as 0600 and those directories as 0700.

    hcl fmt --stdin honors --check and --diff

    terragrunt hcl fmt --stdin ignored --check and --diff. It printed the reformatted HCL and exited 0 whether or not the input needed formatting.

    --check now exits with status code 1 when the input needs formatting, and --diff prints a unified diff labeled old/stdin and new/stdin. Neither flag prints the formatted content, so getting that content back means running --stdin without them.

    hcl validate no longer crashes on errors that carry no source location

    terragrunt hcl validate crashed while formatting its output when one of the errors it found had no position in the configuration. Terragrunt now prints that error's summary and detail, without a location line.

    Fixed the deprecated environment variables for hcl validate

    TG_HCLVALIDATE_STRICT_VALIDATE, the deprecated name for --strict, also turned on --show-config-path. --strict only takes effect alongside --inputs, and --show-config-path cannot be combined with --inputs. With that variable set, terragrunt hcl validate --inputs failed with specifying both -show-config-path and -inputs is invalid.

    TG_HCLVALIDATE_SHOW_CONFIG_PATH, the deprecated name for --show-config-path, was not recognized at all.

    TG_HCLVALIDATE_STRICT_VALIDATE now sets only --strict, and TG_HCLVALIDATE_SHOW_CONFIG_PATH sets --show-config-path. TG_STRICT_VALIDATE, TERRAGRUNT_STRICT_VALIDATE, and TERRAGRUNT_HCLVALIDATE_SHOW_CONFIG_PATH are unchanged.

    Fixed panic on invalid if_disabled value with include block

    A generate block with an invalid if_disabled value combined with an include block caused a nil pointer panic instead of a descriptive error. Terragrunt now returns an error naming the generate block and the invalid value, consistent with if_exists validation.

    OCI sources reject Docker-style :tag suffixes instead of fetching latest

    An oci:// source that pinned a version with a Docker-style suffix, like oci://ghcr.io/acme/modules/vpc:1.0.0, silently ignored the suffix and resolved the latest tag, so a run could fetch a different module version than the one pinned. Terragrunt now validates the registry and repository the same way OpenTofu does and rejects such sources with an error that shows the source rewritten in the supported ?tag=/?digest= form, for example oci://ghcr.io/acme/modules/vpc?tag=1.0.0. Repository names that violate the OCI reference grammar are also rejected before any registry is contacted.

    Prompts accept a piped answer that has no trailing newline

    Piping an answer to a confirmation prompt, as in printf yes | terragrunt run --all destroy, failed with an EOF error because Terragrunt discarded a final answer that ended without a newline. Terragrunt now reads that final answer, and only a prompt that gets no input at all reports EOF.

    Provider cache supports signed provider download URLs

    When a provider mirror returned a signed download URL, the Provider Cache Server used the entire URL, including its query string, as the archive filename. Long authentication parameters could exceed filesystem filename limits and fail with file name too long.

    Terragrunt now derives the archive filename only from the URL path while preserving the query string when downloading it. Signed provider URLs, including archives in nested object paths and relative mirror URLs, now download and cache correctly.

    find and list reject a --queue-construct-as value that holds no command

    A value made only of shell punctuation, such as terragrunt find --queue-construct-as=';', ended the run with a crash report. A value that quotes an empty command, such as --queue-construct-as='""', was accepted even though it names no command.

    find and list now exit with an error that repeats the value you passed and shows what --queue-construct-as expects instead.

    render --write picks a default filename without a format flag

    terragrunt render --write failed with is a directory unless it was paired with --format or --json. Only those flags set the default filename, so a bare --write had no output path and Terragrunt tried to write to the unit directory itself.

    The default now follows the format in use. terragrunt render --write writes terragrunt.rendered.hcl next to the unit configuration, and --json or --format=json writes terragrunt.rendered.json. An explicit --out still takes precedence.

    sops_decrypt_file now uses the credentials your auth provider supplies

    When a run obtained credentials from --auth-provider-cmd, sops_decrypt_file ignored them for any variable already set in the environment Terragrunt started with. The rest of the run honored the auth provider, and correctly overrode any ambient environment variables. OpenTofu/Terraform received those credentials, and so did the AWS calls Terragrunt makes on a unit's behalf, such as get_aws_account_id.

    Decryption now runs as the identity Terragrunt resolved for the unit, the same one the rest of the run uses, regardless of ambient environment variables.

    info strict list <name> now honors --all

    Passing a control name to info strict list shows that control's subcontrols. Unlike the top-level listing, it ignored the --all flag and always included completed subcontrols.

    Terragrunt now applies the same rule when you name a control.

    String inputs reach modules with ${...} intact

    Passing a string input that contains ${...} to a variable declared with a type other than string used to fail with Variables not allowed, because OpenTofu/Terraform parse those values as HCL expressions and read ${...} as an interpolation. Reading a JSON or YAML file into an input hit this whenever the file happened to contain that sequence:

    inputs = {
    config = file("./config.json")
    }
    

    Terragrunt now escapes interpolation sequences in string inputs when the module declares the variable with a type that makes the value parse as HCL, so ${...} arrives as literal text instead of failing the run. Variables declared as string, and variables declared with no type at all, are read verbatim by OpenTofu/Terraform, and their values are still passed through untouched.

    ๐Ÿงช Experiments Updated

    Read dependency outputs directly from Azure state

    The dependency-fetch-output-from-state experiment can now read dependency outputs directly from Azure Storage (azurerm) state, in addition to S3. This avoids initializing the dependency and running tofu output or terraform output.

    Azure direct reads require the azure-backend experiment as well. Unsupported configurations requiring native-only authentication, endpoint, timeout, or customer-provided-key behavior continue to use the native output path.

    When a dependency has no state yet, Terragrunt uses that dependency block's mock_outputs, as it already does for S3. When Azure direct reads resolve a storage account key through Azure Resource Manager, which is the case unless access_key, sas_token, or use_azuread_auth is set, a resource_group_name, storage_account_name, or subscription_id naming a resource that does not exist fails with an error naming those keys rather than substituting mock outputs.

    Read dependency outputs directly from GCS state

    The dependency-fetch-output-from-state experiment can now read dependency outputs directly from GCS state, in addition to S3. This avoids initializing the dependency and running tofu output or terraform output.

    Unsupported GCS configurations continue to use the native output path. When a dependency has no state yet, Terragrunt uses that dependency block's mock_outputs, as it already does for S3.

    Thanks to @joshmyers for the original GCS implementation.

    render previews what an expanded dependency block expanded to

    With the block-iteration experiment enabled, a dependency block that carries an expansion block now renders as it was written, followed by the elements it expanded into, commented out and with their bodies resolved:

    $ terragrunt render --experiment block-iteration
    dependency "aurora" {
    expansion {
    for_each = toset(["web", "api"])
    }
    config_path = "../aurora-${each.key}"
    }
    # Expands to:
    #
    # dependency "aurora" {
    # config_path = "../aurora-api"
    # }
    #
    # dependency "aurora" {
    # config_path = "../aurora-web"
    # }
    

    The elements are comments because they aren't valid Terragrunt HCL configurations (you are not allowed to use the same dependency label twice in Terragrunt configurations), the previews are there to help you understand how expansion will resolve.

    โš™๏ธ Process Updates

    Go bumped to v1.27

    The version of Golang used to compile the Terragrunt binary has been updated from v1.26.6 to v1.27.0.

    If you build Terragrunt from source, or import it as a Go module, you now need a Go 1.27 toolchain.

    OpenTelemetry SDK updated to v1.45.0

    Terragrunt's OpenTelemetry tracing and metrics dependencies have been updated from v1.44.0 to v1.45.0. The logging packages and exporters have also been updated to their compatible releases, and Terragrunt now uses the v1.43.0 semantic conventions.

    Telemetry behavior is unchanged.

    Pull Requests

    โœจ Features

    • feat: Adding interactive scaffold form by @yhakbar in #6615
    • feat(azure): End to end Azure CICD by @denis256 in #6574
    • feat: Adding bare enabled to unit and stack blocks by @yhakbar in #6714
    • feat: Keying stack output addresses by iteration key by @yhakbar in #6715
    • feat: Adding render preview for expansion by @yhakbar in #6737
    • feat: remote state reading for GCP and Azure by @denis256 in #6710

    ๐Ÿ› Bug Fixes

    • fix: Strip credentials before local CLI config write by @yhakbar in #6678
    • fix: Tightening file permissions for generated files by @yhakbar in #6675
    • fix: Reject oci:// sources with docker-style name suffixes instead of resolving latest by @denis256 in #6696
    • fix: Escape interpolation in string inputs when type is verified by @yhakbar in #6685
    • fix: Fixing local copies when symlinks exist from provider caching by @yhakbar in #6684
    • fix(provider-cache): fixed handling arguments in urls by @denis256 in #6680
    • fix(cas): strip go-getter depth query parameter before invoking git by @HalisCz in #6513
    • fix(cas): use venvtest helper in depth query param tests by @denis256 in #6712
    • fix(test): fix for failing test TestNewSignalsForwarderMultipleUnix by @denis256 in #6713
    • fix: prevent nil pointer panic on invalid if_disabled by @denis256 in #6718
    • fix: Fixing --queue-construct-as resulting in empty tokenization by @yhakbar in #6720
    • fix: Fixing render --write when no --format is supplied by @yhakbar in #6724
    • fix: Preventing extremely small or large float exponents from crashing Terragrunt by @yhakbar in #6727
    • fix: Handling situation when diagnostics contain nil range by @yhakbar in #6729
    • fix: Addressing nil Range and Snippet in SourceSnippets by @yhakbar in #6731
    • fix: Propagating flag parse errors instead of swallowing them by @yhakbar in #6732
    • fix: Use the appropriate absolute path to unit config file, not basename when checking version constraints by @yhakbar in #6728
    • fix(security): upgrade Go to 1.27 by @denis256 in #6736
    • fix(test): pass config fixture to setupTest by @denis256 in #6738
    • fix: Fixing info strict list without --all by @yhakbar in #6725
    • fix: Fixing deprecated TG_HCLVALIDATE_STRICT_VALIDATE env var by @yhakbar in #6730
    • fix: Fixing hcl fmt with --stdin combined with --check and/or --diff by @yhakbar in #6726
    • fix: autoinclude config path fixes by @denis256 in #6711
    • fix: Normalize paths using ToSlash to hande old/new prefix appropriately by @yhakbar in #6744
    • fix: Fixing tofu/terraform binary selection for run --all usage by @yhakbar in #6753
    • fix: Updating render --write file permissions by @yhakbar in #6756

    ๐ŸŽ๏ธ Performance

    • perf: Refactor default --tf-path resolution by @yhakbar in #6651

    ๐Ÿ“– Documentation

    • docs: give each environment its own state backup path in the Terralith guide by @yhakbar in #6683
    • docs: serve the Google tag container first-party by @ZachGoldberg in #6699
    • docs: proxy CORS-less GTM tags so they load under Partytown by @ZachGoldberg in #6704
    • docs: Clean-up for v1.1.4 changelog by @yhakbar in #6747

    โœ… Tests

    • test(ci): validate OCI registry authentication by @denis256 in #6662

    ๐Ÿค– CI

    • ci: Add weekly security scans by @denis256 in #6691
    • ci(coverage): added support for test suppressions in weekly test report by @denis256 in #6752

    ๐Ÿงน Chores

    • chore: tests coverage increase by @denis256 in #6668
    • chore(deps): update aws-sdk-go-v2 by @denis256 in #6655
    • chore: runner pool test coverage by @denis256 in #6647
    • chore: A lot more venv plumbing by @yhakbar in #6644
    • chore(deps): bump @astrojs/vercel from 11.0.0 to 11.0.3 in /docs by @dependabot[bot] in #6658
    • chore: docs sync by @denis256 in #6676
    • chore: venv plumbing for cloud SDKs by @yhakbar in #6645
    • chore: Plumbing venv into cloud getters by @yhakbar in #6650
    • chore(deps): update OpenTelemetry SDK to v1.45.0 by @denis256 in #6686
    • chore: Resolve env var defined flags from venv by @yhakbar in #6652
    • chore: Increasing discovery boundary test coverage by @yhakbar in #6671
    • chore: Wire expansion into dependency parse by @yhakbar in #6679
    • chore: Virtualizing util.file.go functions by @yhakbar in #6653
    • chore: Upgrading go to 1.26.6 by @yhakbar in #6697
    • chore: Nesting the dependency cty map by iteration key by @yhakbar in #6689
    • chore(deps): bump github.com/moby/go-archive from 0.2.0 to 0.3.0 by @dependabot[bot] in #6705
    • chore: Increasing venv coverage further by @yhakbar in #6677
    • chore: Adding integration coverage for expanded dependencies by @yhakbar in #6693
    • chore: Use local catalog for TestCatalogWithLocalDefaultTemplate by @yhakbar in #6700
    • chore: Updating TestNewSignalsForwarderMultipleUnix to actually check for the signal by @yhakbar in #6701
    • chore: Use an injectable cap in TestPartialEval_DeeplyNestedExpressionReturnsTypedError by @yhakbar in #6702
    • chore: Moving the TestDiscovery_GraphConcurrentConfigAccessWithRacing in-memory by @yhakbar in #6703
    • chore: Wiring expansion into the unit and stack parse by @yhakbar in #6694
    • chore: Completing venv abstraction by @yhakbar in #6698
    • chore: addressing PR #6736 comemtns by @denis256 in #6741
    • chore: Adding sandboxed unit tests in CI by @yhakbar in #6742
    • chore: Isolate TestDependencyOutputSkipDependencyOutputsFlag fixtures by @yhakbar in #6740
    • chore: Preventing flakes from TestNewSignalsForwarderMultipleUnix by @yhakbar in #6750
    • chore: Adding FS sandboxed unit tests in CI by @yhakbar in #6754
    • chore: Running go fix ./... by @yhakbar in #6758
    Original source
  • Aug 13, 2026
    • Date parsed from source:
      Aug 13, 2026
    • First seen by Releasebot:
      Aug 13, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.3

    Terragrunt ships a broad release with major bug fixes, new experiments, and smoother workflows. It improves dependency mocks, scaffold behavior, provider caching, filtering, and hooks, while adding browse-tui, bounded discovery, mutable generate output, and OCI source support.

    ๐Ÿ› Bug Fixes

    Fixed Unsupported attribute errors for values.* inputs that autoinclude overrides

    A unit input referencing a values.* key that the unit's values file doesn't define no longer fails with Unsupported attribute when an autoinclude block supplies that input. The autoinclude value is applied as intended.

    # stacks/terragrunt.stack.hcl
    unit "subnet" {
      source = "../units/subnet"
      path = "subnet"
      autoinclude {
        dependency "vpc" {
          config_path = unit.vpc.path
          mock_outputs = { vpc_id = "mock" }
        }
        inputs = {
          vpc_id = dependency.vpc.outputs.vpc_id
        }
      }
      values = {
        cidr_block = "10.0.0.0/24"
      }
    }
    # units/subnet/terragrunt.hcl
    inputs = {
      vpc_id = values.vpc_id # supplied by autoinclude, not the values file
      cidr_block = values.cidr_block # still resolves from values file
    }
    

    Fixed overwrite_terragrunt and remove_terragrunt on files with no trailing newline

    generate blocks using if_exists = "overwrite_terragrunt" or if_disabled = "remove_terragrunt" failed to properly handle existing files when the file at the target path had no newline after its first line, empty files included.

    Terragrunt now properly handles files like this, so a file carrying the Terragrunt signature is overwritten or removed as configured, and a file without it produces the usual error naming the path Terragrunt would not touch.

    Dependency mock_outputs apply when the state bucket doesn't exist yet

    When reading a dependency's outputs directly from remote state (--dependency-fetch-output-from-state), Terragrunt fell back to mock_outputs only when the state object was missing, not when the S3 bucket itself didn't exist. A dependency on an environment that hadn't been bootstrapped yet would fail instead of using its mocks.

    A missing bucket is now treated the same as a missing state object, so commands like plan and validate can resolve mocks before the dependency's backend has been created.

    Source permissions preserved on hidden directories copied by include_in_copy

    With the fast-copy strict control enabled, a hidden directory that Terragrunt copied due to include_in_copy matching something within it took the permissions of the first file generated within it, instead of the permissions it had in the source.

    Those directories now keep their source permissions, matching the copy Terragrunt performs with the control disabled.

    Applied the positive half of a filter that begins with a negation

    When a --filter query began with a negation, Terragrunt treated the whole query as an exclusion. The expressions chained after the negation stopped restricting the selection and only narrowed what got subtracted, so components matching none of them came back in the results. Those expressions are now applied.

    $ terragrunt list
    bar baz foo
    $ terragrunt list --filter '!name=foo | name=bar'
    bar baz foo
    $ terragrunt list --filter '!name=foo | name=bar'
    bar
    

    This follows the left-to-right refinement that | has everywhere else: each expression narrows what the one before it selected. A query is only treated as an exclusion when every one of its expressions is negated, such as '!name=foo' or '!name=foo | !name=bar'.

    See Combining Expressions for how negation, intersection and union interact.

    Fixed a race condition that left cached provider archives in the working directory

    With the provider cache server enabled via --provider-cache, a race let the server start responding to requests before it had finished preparing the directories it caches into. A provider requested in that window had its archive and lock file written relative to the working directory instead of into the cache, leaving zip files behind in your project.

    That race condition has been fixed. Providers now always download into the cache directory.

    Fixed a race condition between concurrent Terragrunt runs downloading providers

    A race condition in the logic used to synchronize provider downloads meant that two Terragrunt runs on the same machine could interfere with each other while caching the same provider. Each run staged its downloads at the same path, so a run that finished first could delete an archive another run was still unpacking, failing that run with failed to open zip archive.

    That race condition is now fixed. Two runs can cache the same provider at the same time.

    Fixed space-delimited flag values in providers lock

    The space-delimited form, providers lock -platform linux_amd64, now reaches OpenTofu and Terraform intact. Previously it was the attached form, -platform=linux_amd64, that worked: given the value as a separate argument, Terragrunt moved it to the end of the command, where it was read as a provider address and the run failed with Invalid provider type "linux_amd64".

    -fs-mirror and -net-mirror were moved the same way, and now keep their values too.

    With --provider-cache enabled, platforms are also split correctly across the per-platform providers lock runs used to warm the cache.

    Fixed scaffold on units and stacks

    terragrunt scaffold read every source as an OpenTofu/Terraform module. Given a unit or a stack, which are Terragrunt configurations rather than OpenTofu/Terraform modules, it exited successfully having written an invalid terragrunt.hcl file.

    Units and stacks are now scaffolded the way the Catalog TUI scaffolds them: their files are copied into the working directory for you to edit in place, along with a terragrunt.values.hcl listing every values.* reference the configuration makes.

    terragrunt scaffold 'github.com/gruntwork-io/terragrunt-scale-catalog//units/aws/oidc/iam-oidc-role'
    

    Copying refuses to overwrite: a file that would land on an existing path stops the command before anything is written. Modules and templates are unaffected and are still scaffolded from their variables.

    See Scaffold for what gets copied and how the values file is filled in.

    Answered every prompt when input is piped in

    A run that asks for confirmation more than once, such as terragrunt backend delete prompting for both the lock table entry and the state object, used to read only the first answer when the answers were piped in rather than typed. The remaining answers were discarded while reading ahead, and the next prompt failed with an end-of-input error. Every prompt in a run now reads from the same input, so piping yes for each one works.

    Stack dependencies honor mock_outputs with --dependency-fetch-output-from-state

    A dependency block that reads outputs from a stack (its config_path points at a terragrunt.stack.hcl directory) used to fail when a unit in that stack had no state yet, even when the dependency declared mock_outputs. This blocked commands like plan and validate against a stack that hadn't been applied.

    Such a dependency now falls back to mock_outputs for the units that have no state yet. In a partially applied stack, applied units resolve to their real outputs while the rest use their mocks.

    Mocks for a stack dependency are keyed by unit name, so mock_outputs has to be a map or object. Declaring it as any other type now reports that directly, instead of leaving the units it can't cover out of the stack outputs.

    Fixed --config= being ignored by the tflint hook

    The built-in tflint hook reads the configuration file out of the arguments you give it, then uses that path for tflint init and for the lint run. It only recognized the space-separated --config spelling, so a hook written as:

    before_hook "tflint" {
      commands = ["plan"]
      execute = ["tflint", "--config=custom.tflint.hcl"]
    }
    

    was treated as though no configuration file had been named at all. Terragrunt searched the unit directory and its parents for a .tflint.hcl file instead, and either failed with a config-not-found error or ran tflint init against whatever unrelated configuration the search turned up. Terragrunt now recognizes --config , --config=, -c , and -c=.

    The hook also builds --var arguments from the unit's inputs and from TF_VAR_ entries in extra_arguments blocks. Those arguments came out in a different order on every run, which made the logged command line, and anything comparing it between runs, needlessly unstable. They are now ordered by variable name.

    ๐Ÿงช Experiments Added

    block-iteration experiment reserves the expansion block

    The block-iteration experiment has been added as the gate for iterating a dependency, unit, or stack block over a count or for_each, declared through a nested expansion block, along with an enabled attribute on unit and stack blocks.

    In this release the flag is reserved only, and enabling it has no behavioral effect. Writing an expansion block without the experiment now reports an error naming the flag, rather than leaving the block to be silently discarded:

    the unit "app" block in /path/to/terragrunt.stack.hcl uses an expansion block, which requires the 'block-iteration' experiment; enable it with --experiment block-iteration
    

    Track progress and share feedback in #4504.

    bounded-discovery โ€” Added a directory boundary for graph traversal

    Filter expressions that traverse the dependency graph reach beyond the working directory: dependents (--filter '...{unit}') by walking up to the Git repository root, dependencies (--filter '{unit}...') by following declared paths. Either way, Terragrunt reads and parses every configuration it touches. In monorepos with isolated environments, that traversal can fail or do wasted work reading sibling environments.

    Enable the new bounded-discovery experiment to set a boundary for that traversal. The --discovery-boundary flag (env: TG_DISCOVERY_BOUNDARY) replaces the Git repository root as the enclosure for a whole run:

    cd environments/staging
    terragrunt run --all plan --experiment bounded-discovery --filter '...{vpc}' --discovery-boundary .
    

    The experiment also unlocks an inline (dir) boundary operand, which bounds a single expression and overrides the flag. It occupies the same slot as a traversal depth, so it bounds discovery by location the way a number bounds it by graph hops:

    cd environments/staging
    terragrunt run --all plan --experiment bounded-discovery --filter '(.)...{vpc}'
    

    Any configuration that resolves outside the boundary, whether a dependent or a dependency, is not read, parsed, or returned: find does not list it and run --all does not run it. Configurations inside the boundary are discovered as usual.

    The boundary must be an existing directory, and relative paths are resolved against the working directory. Dependent traversal searches upward from the working directory, so filters that use it also need the boundary to be the working directory or one of its parents. Dependency traversal follows declared paths from the units a filter matched, so dependency-only filters accept any directory, including one below the working directory:

    # From the repository root, follow app's dependencies but keep them within prod
    terragrunt find --experiment bounded-discovery --filter '{./prod/app}...' --discovery-boundary ./prod
    

    Reserving ( and ) for the boundary operand changes how --filter reads those characters everywhere, not only when the experiment is enabled. An expression such as --filter '1...(foo | bar)' previously matched a unit literally named (foo or bar); it is now rejected as a malformed boundary. Wrap a name or path containing parentheses in braces (e.g. --filter '{./weird(name)}') to keep it literal.

    browse-tui โ€” Added an interactive browser for your estate

    The new browse-tui experiment adds the terragrunt browse command. With the experiment enabled, terragrunt browse opens a three-column Terminal User Interface (TUI) browser of your infrastructure estate: the parent directory on the left, the current directory in the middle, and a detail pane on the right showing metadata for the highlighted unit, stack, or directory. The browser opens immediately and fills in metadata as discovery completes in the background.

    Enable it with --experiment browse-tui or TG_EXPERIMENT=browse-tui. See the experiment documentation for the keybindings, search, and the criteria for stabilization.

    mutable-generate โ€” Deduplicated generate block output

    The mutable-generate experiment has been added. With it enabled, the contents a generate block produces are stored in the Content Addressable Store (CAS), and the file written at path is a read-only link to that stored copy rather than a file of its own.

    Since the stored copy is addressed by the hash of its contents, anything generating identical contents links to the same copy. A generate block inherited by several hundred units therefore costs one copy in .terragrunt-cache rather than several hundred.

    The link is read-only because that copy is shared. Where a generated file does need to be edited in place, a new mutable attribute on the generate block gives it a writable file of its own:

    generate "provider" {
      path = "provider.tf"
      if_exists = "overwrite"
      mutable = true
      contents = "..."
    }
    

    Setting mutable without the experiment enabled is an error, since earlier Terragrunt versions reject the attribute. The CAS is required, so --no-cas writes generated files directly and mutable has no effect.

    For details, see the experiment documentation.

    optional-dependency-outputs โ€” Added --no-dependency-outputs flag to skip dependency output resolution

    Added a --no-dependency-outputs flag that skips all dependency output resolution globally, mirroring the existing skip_outputs = true attribute on individual dependency blocks.

    The feature is gated behind the optional-dependency-outputs experiment:

    TG_EXPERIMENT=optional-dependency-outputs terragrunt run --no-dependency-outputs -- init
    

    Using --no-dependency-outputs without enabling the optional-dependency-outputs experiment will return an error.

    Thanks to @pjrm for contributing this feature!

    ๐Ÿงช Experiments Updated

    catalog-format โ€” Added reading the catalog as JSON Lines

    The catalog command draws a terminal user interface, and refuses to start where there is no terminal to draw it on. With the catalog-format experiment enabled, --format=jsonl writes the same discovery to standard output instead, as one JSON object per line:

    terragrunt catalog --experiment=catalog-format --format=jsonl | jq -c '{kind, title, component_source}'
    

    Entries are written as they are discovered rather than collected first, so output is readable while the remaining repositories are still loading, and a reader that stops early ends the command quietly:

    terragrunt catalog --experiment=catalog-format --format=jsonl | head -5
    

    Note

    Closing the pipe

    In this example, the head program exits after reading in five lines, and Terragrunt detects the SIGPIPE signal from the OS, and shuts down cleanly.

    Entries appear in discovery order, which interleaves the repositories being loaded and differs between runs. Every entry carries the complete body of the component's README in the doc field. Combine usage of Terragrunt with other tools like jq to drop it.

    terragrunt catalog --experiment=catalog-format --format=jsonl | jq -c 'del(.doc)'
    

    Entries follow a published JSON schema. For the fields and their meanings, see Non-interactive catalog.

    --format=tui is the default, and leaves the terminal user interface exactly as it was.

    catalog-format โ€” Added reading the catalog as Markdown

    The catalog-format experiment gains a second non-interactive format. Where --format=jsonl writes a record per catalog entry for a program to parse, --format=md writes one Markdown document for a person or an agent to read:

    terragrunt catalog --experiment=catalog-format --format=md &gt; catalog.md
    

    Each entry becomes a section holding the metadata the catalog user interface shows for it, the source the component is scaffolded from, and the component's README. Sections are written as entries are discovered, so the document is readable while the remaining repositories are still loading.

    READMEs are reproduced inside fenced blocks, so the headings one carries are not read as sections of the catalog document. The document closes with a table naming every component it holds and a count of what was discovered, which is how a reader tells a complete document from one that was cut short by a consumer that stopped reading.

    For the fields each section carries, see Non-interactive catalog.

    oci โ€” Added OCI sources for stack units and stacks

    terragrunt.stack.hcl now accepts oci:// sources in unit and stack blocks, so a stack can pull its components straight from an OCI registry. Without the oci experiment enabled, such a source fails with a clear error instead of an unsupported-scheme failure.

    oci โ€” Added OpenTofu CLI-config credentials for OCI module sources

    oci:// module downloads now read OpenTofu's CLI-config credentials, so one configuration serves both OpenTofu and Terragrunt.

    Terragrunt honors the oci_credentials "[/]" blocks (username and password, OAuth tokens, or a docker_credentials_helper, which like tofu may only be set on a whole registry) and the oci_default_credentials fallback helper. A TF_CLI_CONFIG_FILE or TERRAFORM_CONFIG value selects the config file outright; otherwise Terragrunt reads the first of ~/.tofurc and ~/.terraformrc that exists, and merges the *.tfrc and *.tfrc.json files in OpenTofu's config directory.

    Terragrunt picks the most specific matching source across CLI config and ambient Docker config; an explicit CLI-config entry wins when both match equally. Set discover_ambient_credentials = false in the oci_default_credentials block to use CLI config only.

    โš™๏ธ Process Updates

    Go bumped to v1.26.5

    The version of Golang used to compile the Terragrunt binary has been updated from v1.26.0 to v1.26.5.

    Thanks to @apoiget for contributing this upgrade!

    Pull Requests

    โœจ Features

    • feat: Adding graph boundary via () syntax by @yhakbar in #6365
    • feat: Adding --discovery-boundary flag by @yhakbar in #6355
    • feat(getter): OpenTofu CLI-config credentials for oci:// sources by @denis256 in #6531
    • feat: Adding browse by @yhakbar in #6219
    • feat: Adding mutable attribute to the generate block by @yhakbar in #6563
    • feat: Adding md format for catalog by @yhakbar in #6608
    • feat: Add --skip-dependency-outputs flag to skip dependency output resolution by @pjrm in #6422

    ๐Ÿ› Bug Fixes

    • fix(providercache): log -lockfile=readonly skip at debug level by @bryanhorstmann in #6577
    • fix: Fixing handling of EOF in generate blocks by @yhakbar in #6592
    • fix: Fixing the --config= form of flags used in the tflint hook by @yhakbar in #6591
    • fix: Fixing fast-copy ancestor directory permissions by @yhakbar in #6593
    • fix: Addressing providers lock -platform usage with space delimited values by @yhakbar in #6597
    • fix: Fixing bug with negation | positive expression in the same query. by @yhakbar in #6598
    • fix: Fixing provider cache server archive dir race by @yhakbar in #6620
    • fix: Fixing scaffold on units and stacks by @yhakbar in #6607
    • fix: Addressing feedback from #6565 and #6605 by @yhakbar in #6628
    • fix: autoinclude values override for inputs by @denis256 in #6626
    • fix: Fixing md format catalog escaping by @yhakbar in #6638
    • fix: Plumbing through evalCtx for discovery boundary by @yhakbar in #6632
    • fix: Fixing stack dependency mock outputs by @yhakbar in #6530
    • fix: Fixing issue where dependency mock outputs aren't used when bootstrapping hasn't run yet. by @yhakbar in #6534

    ๐ŸŽ๏ธ Performance

    • perf: Reducing allocations in tree parse by @yhakbar in #6648

    ๐Ÿ“– Documentation

    • docs: Add call out for terragrunt scale in quick start by @yhakbar in #6583
    • docs: document oci module sources, authentication, and caching by @denis256 in #6636
    • docs: Cleaning up changelog for v1.1.3 by @yhakbar in #6669
    • docs: Cleaning up experiment docs by @yhakbar in #6627
    • docs: address review feedback on the oci and autoinclude docs by @denis256 in #6643

    โœ… Tests

    • test(getter): integration tests against a local OCI distribution registry by @denis256 in #6614
    • test: prove oci module portability between tofu and terragrunt by @denis256 in #6629
    • test(git): add unit coverage for internal/git command wrappers and parsers by @denis256 in #6661

    ๐Ÿงน Chores

    • chore: Pin exact provider versions for terralith to terragrunt guide by @yhakbar in #6578
    • chore: Using vfs handle for ParseFromFile by @yhakbar in #6561
    • chore: Walk in discovery with vfs by @yhakbar in #6564
    • chore: Registring catalog-format experiment by @yhakbar in #6582
    • chore(deps): update AWS, Azure, GCP SDKs by @denis256 in #6590
    • chore: Continuing clean-up of go test ./... on a fresh clone of the repo by @yhakbar in #6553
    • chore: Fixing usage of deprecated aws sdk by @yhakbar in #6600
    • chore: Cleaning up profile tests per feedback in #6553 by @yhakbar in #6599
    • chore: Clean-up by @yhakbar in #6584
    • chore: Addressing feedback from #6365 and #6355 by @yhakbar in #6603
    • chore: Register the block-iteration experiment by @yhakbar in #6562
    • chore: address review feedback from #6531 by @denis256 in #6609
    • chore: Addressing flake in TestCatalogJSONLFormatCleansUpOnEarlyExit by @yhakbar in #6613
    • chore: updated TestDiscovery_GraphConcurrentConfigAccessWithRacing to use VFS by @denis256 in #6622
    • chore: Adding expansion detection and internal expansion logic by @yhakbar in #6565
    • chore: Adding expansion blocks to the configs that accept expansion by @yhakbar in #6605
    • chore: Threading venv through getters and hcl fmt by @yhakbar in #6621
    • chore: Addressing feedback from #6621 by @yhakbar in #6633
    • chore: Fixing experiment tag in sidebar by @yhakbar in #6635
    • chore: Updating mem exec so that it fails closed by @yhakbar in #6634
    • chore: Gate real hg usage test behind the exec build flag by @yhakbar in #6637
    • chore: Replacing aws provider with null provider in init-cache fixture by @yhakbar in #6639
    • chore: Cleaning up NewParsingContext constructor by passing in venv as a param by @yhakbar in #6630
    • chore: Addressing lint finding by @yhakbar in #6649
    • chore: Refactoring markdown deps into internal/md by @yhakbar in #6640
    • chore: Adding unit tests for internal packages by @denis256 in #6660
    • chore: Bumping Go to 1.26.5 (#6664) by @apoiget in #6666
    • chore: Dropping stale tree parse test case by @yhakbar in #6672
    Original source
  • Jul 29, 2026
    • Date parsed from source:
      Jul 29, 2026
    • First seen by Releasebot:
      Jul 30, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.2

    Terragrunt releases faster parent-folder lookups, a new ctrl+d scaffold flow in the catalog README view, and several reliability fixes for roles, local sources, logging, feature defaults, and provider cache downloads. It also adds experimental OpenTelemetry logs, profiling, OCI source support, and Azure remote state management.

    โœจ New Features

    Scaffold straight from the catalog README view with ctrl+d

    In the terragrunt catalog TUI, pressing ctrl+d while reading a component's README now scaffolds it immediately, skipping the interactive form. Module and template inputs are written as # TODO placeholders, and unit/stack copies get a fully placeholder terragrunt.values.hcl. The hint bar at the bottom of the README view advertises the new key.

    ๐ŸŽ๏ธ Performance Improvements

    Fewer filesystem checks when resolving find_in_parent_folders()

    find_in_parent_folders() walks up from a unit toward the filesystem root, checking each directory for the configuration file it was asked to find. Even when the call named a file, as in find_in_parent_folders("root.hcl"), each directory along the way was also checked for the default configuration filenames. Units sharing a parent chain then repeated every check their siblings had already made.

    Terragrunt now checks only the filename the call names, and reuses what it already learned about a directory for the rest of the command. Deeply nested estates benefit most, since every level between a unit and its root configuration used to be re-checked once per unit.

    In micro-benchmarks, resolving the root configuration for 100 units nested eight directories deep went from 4.8ms to 0.49ms. Across the benchmarked shapes the lookups run between 7x and 10x faster, and the time saved grows with both the number of units and how deeply they sit below their root configuration.

    ๐Ÿ› Bug Fixes

    Fixed roles assuming themselves for backend operations

    A regression in v1.1.1 broke setups that provide static AWS credentials and configure a role via the iam_role attribute, the --iam-assume-role flag, or TG_IAM_ASSUME_ROLE.

    In those setups, Terragrunt assumes the role once at the start of a run, and every later AWS call uses that role session. In v1.1.1, backend operations like bootstrapping the state bucket started performing an extra role assumption of their own. Since the run was already using the role session at that point, the role tried to assume itself, and AWS rejected the call with an AccessDenied error unless the role's trust policy happened to include the role itself.

    Backend operations now reuse the role session from the start of the run, as they did before v1.1.1.

    This does not affect the assume_role attribute of the remote_state block. Roles configured there are backend-specific and are still assumed on top of the supplied credentials, so the cross-account role assumption should continue to work as expected.

    Local sources no longer re-init when uncopied files change

    For units with a local source, Terragrunt decides whether the cached copy is stale by hashing the source directory. That hash previously covered every file in the directory, including hidden files and exclude_from_copy matches that are never copied into the cache. Creating or touching such a file (an editor swap file, a scratch note) changed the hash, forcing a needless re-copy and auto-init on the next run.

    The hash now covers only the files a copy would deliver, honoring the default hidden-file rule along with include_in_copy and exclude_from_copy. Files that never reach the cache no longer trigger re-initialization.

    Fixed width truncation of colored and multi-byte log content

    The width option in a custom log format sizes a column to a fixed number of visible characters. When the content held color codes or multi-byte characters and was longer than the column, truncation cut the raw bytes: it could slice through the middle of a color code, leaving color bleeding into the rest of the line, or split a multi-byte character into invalid output, and it dropped more visible text than the configured width.

    width now measures and cuts by visible characters. Color codes are preserved intact, multi-byte characters are never split, and the column keeps exactly the requested number of visible characters.

    Provider cache downloads now require a secret URL

    The Provider Cache Server now hardens the download endpoint that fetches provider archives on the caller's behalf. That endpoint attaches whatever registry credentials are configured for the upstream host, and it was the only one on the server that did not require the token generated for the run, so any other process on the machine could use a running cache server to pull artifacts from a private registry with the credentials of whoever started the run.

    The download URLs handed to OpenTofu and Terraform now carry a secret path segment, generated fresh each time the cache server starts and redacted from the server's own logs. Requests that omit the segment get a 404.

    Run report no longer mangles the names of paths that share a prefix with the working directory

    When a run's path shared a string prefix with the working directory without being nested under it, the run report shortened its name by shearing off the prefix mid-segment. A working directory of /repo/project alongside a run at /repo/project-staging/unit produced the name -staging/unit.

    The report now shortens a path only when it is genuinely nested under the working directory. Sibling paths keep their full name.

    Feature flag defaults no longer leak between units in run --all

    A feature block's default was recorded once per run and shared by every unit. During run --all, the first unit to be parsed set the value for a flag name, so a unit defining default = false could evaluate feature.toggle.value as true because a sibling unit was parsed first. Which unit won depended on parsing order, making the result vary between runs.

    Defaults are now resolved per unit, including defaults inherited through include. Overrides passed with --feature or TG_FEATURE continue to apply to every unit in the run.

    Thanks to @dhotcolorado for reporting and fixing this!

    Fixed S3 source downloads under EKS Pod Identity

    Downloading unit sources from private S3 buckets (s3::https://...) now works when EKS Pod Identity is the only credential source. Previously, the bundled aws-sdk-go v1 rejected the Pod Identity Agent endpoint (169.254.170.23) because it only allowed loopback hosts. Terragrunt now uses aws-sdk-go v1.55.6, which allows the EKS and ECS container credential endpoints.

    ๐Ÿงช Experiments Added

    otel-logs experiment exports logs to OpenTelemetry

    Terragrunt previously emitted only traces and metrics, so there was no way to ship its log output to an OpenTelemetry backend or correlate log lines with the spans of a failed run.

    Enable the new otel-logs experiment to add an OpenTelemetry logs signal, configured with TG_TELEMETRY_LOGS_EXPORTER:

    • none - no log exporting, the default.
    • console - write log records to the console as JSON.
    • otlpHttp - export logs to an OpenTelemetry collector over HTTP.
    • otlpGrpc - export logs to an OpenTelemetry collector over gRPC.

    TG_TELEMETRY_LOGS_EXPORTER=otlpHttp terragrunt run --all --experiment otel-logs -- apply

    The OTLP exporters read the endpoint from the standard OTEL_EXPORTER_OTLP_ENDPOINT environment variable. Set TG_TELEMETRY_LOGS_EXPORTER_INSECURE_ENDPOINT=true to disable TLS when collecting locally. Records emitted while a span is active carry its trace and span IDs, so a failed unit's logs link to its span in the backend. Without the experiment enabled, the logs exporter stays inert regardless of TG_TELEMETRY_LOGS_EXPORTER.

    profiling experiment adds pprof collection for Terragrunt runs

    Enable the new profiling experiment to collect CPU profiles, memory (heap) profiles, and goroutine profiles (stack traces of all goroutines) using CLI flags. Profiling is intended for debugging the performance of Terragrunt itself, and for exploring ways to optimize Terragrunt as an application; it will not help with improving the performance of the infrastructure Terragrunt manages.

    Example:

    terragrunt --experiment=profiling --profile-cpu cpu.prof --profile-mem mem.prof --profile-goroutine goroutine.prof run -- plan
    

    Use --profile-dir to collect all profiles into a single directory with conventional names (terragrunt_cpu.prof, terragrunt_mem.prof, terragrunt_goroutine.prof):

    terragrunt --experiment=profiling --profile-dir /tmp/profiles run --all -- plan
    

    The same behavior is available via environment variables when the profiling experiment is enabled:

    • TG_PROFILE_CPU
    • TG_PROFILE_MEM
    • TG_PROFILE_GOROUTINE
    • TG_PROFILE_DIR

    When using --profile-dir or TG_PROFILE_DIR, Terragrunt also sets TOFU_CPU_PROFILE for each unit so downstream OpenTofu processes (OpenTofu 1.11 or later) write their own CPU profiles into unit-specific subdirectories. An explicitly set TOFU_CPU_PROFILE is never overridden.

    ๐Ÿงช Experiments Updated

    azure-backend now manages Azure Storage remote state

    The azure-backend experiment now enables functional Terragrunt support for the Azure Storage (azurerm) remote-state backend.

    When the experiment is enabled, Terragrunt can bootstrap the resource group, storage account, and blob container used by remote_state { backend = "azurerm" }, detect whether the backend needs bootstrapping, converge blob versioning and soft-delete settings, delete state blobs or containers, and migrate state blobs within the same storage account.

    Terragrunt-only settings such as location, the storage account SKU options, the skip_* flags, enable_soft_delete, soft_delete_retention_days, and msi_resource_id are consumed by Terragrunt and removed before it runs OpenTofu/Terraform with init -backend-config, so the underlying azurerm backend receives only keys it understands. msi_resource_id is not bootstrap-only: it also selects the managed identity used for delete and migrate.

    This remains opt-in while the experiment is active:

    terragrunt --experiment azure-backend run -- plan
    

    Thanks to @omattsson for driving this support forward.

    oci - Credential helpers for OCI module sources

    oci:// module downloads now use the Docker credential helpers you already have configured, so registries like Amazon ECR authenticate automatically with no extra setup.

    oci - Content-addressable caching for OCI module sources

    oci:// module sources now integrate with Content Addressable Storage. When the oci experiment is enabled, downloads are cached by their manifest digest, so a repeated fetch of the same tag or digest is served from the local store instead of re-downloaded from the registry.

    Mutable tags stay correct: every fetch re-resolves the tag to its current manifest digest at download time, so re-pushing a module under the same tag invalidates the cache and pulls the new content rather than serving a stale copy. A digest-pinned source (?digest=sha256:...) skips registry resolution and keys the cache directly.

    oci - Downloading modules from OCI registries

    The oci experiment now downloads source code (including OpenTofu modules) from OCI Distribution registries. When enabled, Terragrunt accepts oci:// source URLs in Terragrunt configurations (including terraform.source attributes). Specify either tag or digest; omitting both selects the latest tag. //subdir selectors are supported. Artifacts follow the same publishing contract OpenTofu 1.10 consumes natively.

    Authentication covers static credentials via interim TG_TMP_OCI_* environment variables and read-only ambient discovery of Docker and containers auth files. Static credentials can be limited to one registry with TG_TMP_OCI_REGISTRY; without it, the configured token or username and password may be offered to any registry the process contacts. Credential helpers (such as ecr-login) are not invoked yet, so registries that need per-run token minting only work while an externally obtained login is present in an ambient file.

    When the experiment is disabled, oci:// sources remain unsupported.

    For setup steps, see the experiment documentation.

    Pull Requests

    โœจ Features

    feat(getter): implement OCIGetter.Get with fake-store unit tests by @denis256 in #6479
    feat: Add otel-logs experiment by @yhakbar in #6279
    feat(getter): add static and ambient OCI credential discovery by @denis256 in #6483
    feat(getter): add WithOCI and gate oci sources behind the oci experiment by @denis256 in #6486
    feat(getter): add OCI digest CAS resolver with tag re-resolution by @denis256 in #6503
    feat: Adding earlier catalog bail by @yhakbar in #6493
    feat(profiling): add automatic pprof collection by @denis256 in #5711
    feat(getter): credential helpers for oci:// module sources by @denis256 in #6508
    feat: add experimental azurerm remote state backend by @denis256 in #6428

    ๐Ÿ› Bug Fixes

    fix: Fixing docs TF_TOKEN_* rendering by @yhakbar in #6509
    fix: Preventing spurious re-inits by @yhakbar in #6504
    fix: Fixing log truncation by @yhakbar in #6526
    fix: support EKS Pod Identity for S3 source downloads by @denis256 in #6532
    fix: Isolate feature defaults per unit in run --all by @dhotcolorado in #5995
    fix: Adding random URL segment to download URI by @yhakbar in #6547
    fix: Fixing report path prefix trim by @yhakbar in #6527
    fix: Fixing self-chained role assumption by @yhakbar in #6521
    fix: prevent auto-init env vars from leaking into main command by @yapret in #6576

    ๐ŸŽ๏ธ Performance

    perf: Memoize find_in_parent_folders() by @yhakbar in #6545

    ๐Ÿ“– Documentation

    docs: Adding CLI flag precedence rule by @yhakbar in #6524
    docs: Adding changelog entry for #5995 by @yhakbar in #6548
    docs: Re-organizing content related to the run queue out of stack documentation by @yhakbar in #6114
    docs: Adding search telemetry by @yhakbar in #6555
    docs: Improving docs by addressing frequently asked questions by @yhakbar in #6560

    ๐Ÿงน Chores

    chore: Log Windows console mode retrieval failures at debug level (#6374) by @AgustinSabalza in #6376
    chore: Fixing code fences on /reference/hcl/blocks/ by @yhakbar in #6485
    chore: Avoid package-level module resolution for version attribute by @yhakbar in #6482
    chore: AWS dependencies bump by @denis256 in #6502
    chore: Running fd -tf -e go -x golines -w to avoid run-on lines by @yhakbar in #6484
    chore: Adding some integration testing for the version attribute by @yhakbar in #6487
    chore: Unify Venv struct by dropping cas.Venv by @yhakbar in #6488
    chore: Adding vsops by @yhakbar in #6506
    chore: speed up slowest tests with unit-level coverage and hermetic fixtures by @denis256 in #6436
    chore: lint fixes by @denis256 in #6518
    chore(deps): bump astro from 7.0.4 to 7.1.0 in /docs by @dependabot[bot] in #6515
    chore: Fixing panic in Windows test by @yhakbar in #6536
    chore: Update grpc, x/mod, go-shellwords deps by @denis256 in #6543
    chore: Adding more tests for build metadata by @yhakbar in #6538
    chore: Adding vhttp client to abstract away HTTP client connections by @yhakbar in #6121
    chore: Cleaning up tests for #6547 by @yhakbar in #6549
    chore: Refactor for network isolation in tests by @yhakbar in #6507
    chore: fixed failed lint tests by @denis256 in #6550
    chore: Fixing pprof venv access by @yhakbar in #6556
    chore: Updating Kapa integration by @yhakbar in #6558
    chore: coverage report fixes by @denis256 in #6557
    chore: Reducing race in vexec testing by @yhakbar in #6551

    Original source
  • Jul 14, 2026
    • Date parsed from source:
      Jul 14, 2026
    • First seen by Releasebot:
      Jul 15, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.1

    Terragrunt ships a broad release of bug fixes, safer backend and dependency handling, stricter Git and lockfile behavior, and better run --all stability. It also adds new OCI and version-attribute experiments plus friendly crash reports for easier troubleshooting.

    ๐Ÿ› Bug Fixes

    Chained role assumption for the S3 backend

    When AWS credentials were supplied through --auth-provider-cmd or environment variables, Terragrunt ignored the assume_role attribute of the remote_state block for its own backend operations, such as bootstrapping the state bucket. In cross-account setups this caused access errors, even though OpenTofu/Terraform itself assumed the role correctly during runs.

    Terragrunt now uses the supplied credentials as the source identity and assumes the configured role on top of them. The same applies to roles configured via the iam_role attribute or the --iam-assume-role flag, and to fetching dependency outputs directly from S3 state.

    Safer temporary clone directories for terragrunt catalog

    Terragrunt now creates a fresh temporary clone directory for each catalog load, rejects symlinked clone roots, and removes catalog clones when the TUI session exits.

    Resolve dependency outputs for units that reference a dependency in a hook, extra_arguments, or remote_state block

    Resolving a unit's dependency outputs for a downstream unit no longer fails when that unit references its own dependency in:

    • a before_hook, after_hook, or error_hook
    • an extra_arguments block
    • a remote_state block

    Previously these raised There is no variable named "dependency" on the downstream unit, and a remote_state reference could crash Terragrunt.

    Limit IaC engine archive extraction

    Terragrunt now protects developer machines and CI runners from engine archives that expand into unexpectedly large amounts of data. If an IaC engine package is unusually large or contains too many files, Terragrunt stops processing it before it can consume excessive disk space.

    --filter-allow-destroy with ...[] dependent-traversal filters no longer fails

    --filter-allow-destroy --filter '...[HEAD~1...HEAD]' failed with "Too many command line arguments" or hung when the deleted unit had dependents. Terragrunt now correctly plans and destroys deleted units regardless of whether dependents are included in the run.

    Fix find and list missing units inside generated stacks for Git-based filters

    terragrunt find and terragrunt list with a Git-based filter (for example --filter '[HEAD^1...HEAD]') now detect units inside generated stacks. Previously they did not generate stacks in the worktrees they create for the comparison, so no unit nested in a generated stack was ever surfaced, while terragrunt run --all with the same filter targeted those units correctly.

    This affected every change that lands inside a generated stack, including a modified terragrunt.stack.hcl, a change to a unit's own files, and a change to a file the stack reads via read_terragrunt_config or mark_glob_as_read.

    Stacks are generated only inside the comparison worktrees; find and list still do not generate stacks in your current working directory by default.

    Treat Git source ref values strictly as references

    Terragrunt now passes the ref from a Git module source to git strictly as a reference when downloading through content-addressable storage. Previously a source whose ref began with a git option (for example a value starting with --) could be interpreted by git as an option rather than a reference while fetching the source.

    Terragrunt now terminates git option parsing before the repository and reference arguments in its fetch, clone, and ls-remote invocations, so these values can only ever be read as the repository and reference they are meant to be. Normal refs, branches, tags, and commit SHAs continue to work unchanged.

    hcl validate resolves get_original_terragrunt_dir() to the discovered unit

    terragrunt hcl validate and terragrunt hcl validate --inputs now resolve get_original_terragrunt_dir() to each discovered unit's own directory instead of the directory the command was launched from. Previously, when the command ran from a parent directory that discovered units in subdirectories, any read_terragrunt_config() call that built a path relative to get_original_terragrunt_dir() resolved against the wrong directory and failed with "You attempted to run terragrunt in a folder that does not contain a terragrunt.hcl file", even though plan, apply, and run validate worked on the same configuration.

    Both commands now set the original config path per discovered unit before parsing, matching the behavior of run and backend bootstrap, so relative paths resolve against the unit that owns them.

    Respect -lockfile=readonly during provider caching

    When you pass -lockfile=readonly to init, Terragrunt no longer generates or updates .terraform.lock.hcl while warming the provider cache. Previously the cache step could write the lock file before OpenTofu/Terraform ran, so the read-only check always passed and silently defeated the flag.

    Terragrunt now leaves the lock file untouched and lets OpenTofu/Terraform enforce it, failing when the lock file is missing or incomplete. The flag is honored whether it is supplied on the command line or through the TF_CLI_ARGS or TF_CLI_ARGS_init environment variables.

    run --all no longer crashes on dependency discovery with graph filters

    Running run --all with a filter that expands a git range through the dependency graph (for example [HEAD~1...HEAD]...) could fail during dependency discovery, reporting that a component "is missing its working directory". Whether it happened depended on the size and shape of the changed unit's dependency closure, so the same filter succeeded on smaller branches and find was unaffected.

    A dependency reached from several units at once could become visible to discovery before its working directory was set, so a concurrent traversal could read it before it was complete. Dependencies now have their working directory set before they become visible, so run --all behaves the same regardless of graph size.

    terraform_binary respected by run --all when both tofu and terraform are on PATH

    run --all ignored a unit's terraform_binary setting and fell back to the auto-detected default (OpenTofu when both binaries are on PATH). The per-unit options used to execute each unit are cloned from the stack options, whose binary path is the auto-detected default, and the configured value was never applied to them.

    Each unit now honors its own terraform_binary, matching the behavior of a single run. Setting --tf-path or TG_TF_PATH still takes precedence over the config value.

    S3 bucket creation failures report the underlying error

    When creating the state bucket failed during backend bootstrap, the reported error was a misleading NoSuchBucket from a follow-up access check, hiding the actual cause. The original creation error, such as AccessDenied, is now part of the reported message.

    Allow empty locals blocks in terragrunt.stack.hcl

    Fixed a bug where an empty locals {} block in a stack configuration could break stack generate.

    Clear error when terraform.source references a dependency output

    A terraform.source that references dependency.<name>.outputs.<key> is now rejected with a message explaining that the module source must be resolvable before dependencies are evaluated.

    Terragrunt resolves the source while discovering units and building the run queue, before any dependency has run, so such a source can never be satisfied. Previously it surfaced a cryptic decode error.

    ๐Ÿงช Experiments Added

    oci - Module sources from OCI registries

    The oci experiment has been added as the gate for downloading source code (including OpenTofu modules) from OCI Distribution registries using oci:// schema URLs in Terragrunt configurations (including terraform.source attributes). This targets the same registries OpenTofu 1.10 supports natively, such as Amazon ECR, GitHub Container Registry, Azure Container Registry, Google Artifact Registry, and self-hosted or air-gapped registries.

    Enabling the experiment has no behavioral effect yet: the getter that will resolve oci:// sources is not wired into source downloading, so oci:// sources still fail to download. Functional support will land in follow-up releases, gated by this experiment.

    For setup steps, see the experiment documentation.

    version-attribute - Resolve registry modules from a version constraint

    The version-attribute experiment has been added to gate a new version attribute on the terraform block. It holds a version constraint (such as ~> 3.3 or >= 1.0.0, < 2.0.0) for a tfr:// registry module, and Terragrunt resolves it to the highest published version that satisfies the constraint before downloading:

    terraform {
      source = "tfr://registry.opentofu.org/terraform-aws-modules/vpc/aws"
      version = "~&gt; 3.3"
    }
    

    This brings the terraform block to parity with the version argument on OpenTofu and Terraform module blocks. The attribute applies to tfr:// sources only, and cannot be combined with an inline ?version= on the same source.

    Enable it with --experiment version-attribute. For setup steps and the criteria for stabilization, see the experiment documentation.

    โš™๏ธ Process Updates

    Friendly panic reports

    Terragrunt now writes a terragrunt-crash-YYYYMMDDTHHMMSSZ-<pid>.log file when it crashes.

    The report includes runtime details, the command line, the panic message, and the stack trace. You can conveniently share this file (after reviewing for sensitive information) to report panics if Terragrunt crashes.

    Pull Requests

    โœจ Features

    feat: terragrunt panic reporting by @denis256 in #6120

    feat(experiment): introduce oci experiment flag for OCI module sources by @denis256 in #6461

    feat(getter): add OCIGetter by @denis256 in #6478

    feat: Add and validate the version attribute on the terraform block by @yhakbar in #6475

    feat: Gate and resolve the version constraint at download time by @yhakbar in #6477

    ๐Ÿ› Bug Fixes

    fix: Avoiding generation of the lockfile when users supply -lockfile=readonly by @yhakbar in #6358

    fix: Fixing combination of --filter-allow-destroy with graph + Git expression combo by @yhakbar in #6322

    fix: use updated/correct GTM tag by @ZachGoldberg in #6439

    fix(engine): limit engine ZIP archive extraction by @denis256 in #6437

    fix: Generate stacks in worktrees generated for find/list by @yhakbar in #6362

    fix(catalog): harden temporary clone paths by @denis256 in #6438

    fix(git): pass repository and ref as positionals in git fetch, clone, and ls-remote by @denis256 in #6452

    fix(hcl-validate): set per-unit OriginalTerragruntConfigPath so get_original_terragrunt_dir() resolves correctly by @denis256 in #6445

    fix: Dependency output resolution for more scenarios by @yhakbar in #6425

    fix: Prevent terraform_binary from being ignored in run --all by @yhakbar in #6460

    fix: Fixing chained role assumption for backend by @yhakbar in #6327

    fix: Fixing empty locals block for stack generate by @yhakbar in #6470

    fix: Fixing run --all with graph expression throwing on missing working dir by @yhakbar in #6474

    ๐Ÿ“– Documentation

    docs: Fixing docs builds by @yhakbar in #6434

    docs: Documenting the version-attribute experiment by @yhakbar in #6476

    docs: Changelog fix-up by @yhakbar in #6481

    ๐Ÿงน Chores

    chore(deps): bump actions/cache from 5.0.5 to 6.1.0 by @dependabot[bot] in #6430

    chore(deps): bump mikepenz/action-junit-report from 6.4.1 to 6.4.2 by @dependabot[bot] in #6431

    chore(deps): bump the js-dependencies group across 1 directory with 4 updates by @dependabot[bot] in #6432

    chore: Addressing weekly reports (2026-06-29) by @yhakbar in #6435

    chore: Move env to venv by @yhakbar in #6406

    chore: Cleaning up #6406 by @yhakbar in #6462

    chore(deps): bump golang.org/x/crypto in /test/flake by @dependabot[bot] in #6464

    chore: Register the version attribute experiment by @yhakbar in #6463

    chore(deps): bump the js-dependencies group across 1 directory with 6 updates by @dependabot[bot] in #6456

    chore: Cleaning up leaking buckets by @yhakbar in #6466

    chore(deps): bump the go-dependencies group across 1 directory with 15 updates by @dependabot[bot] in #6457

    chore(deps): bump docker/setup-docker-action from 5.2.0 to 5.3.0 by @dependabot[bot] in #6454

    chore: add separated us-west-2 pass by @denis256 in #6473

    chore: OCI container fix by @denis256 in #6465

    chore: Resolve registry module versions from a constraint by @yhakbar in #6471

    chore: Moving writers to venv by @yhakbar in #6410

    chore: Removing source from version attribute error by @yhakbar in #6480

    ๐Ÿ“ Other Changes

    Revert "chore(deps): bump the js-dependencies group across 1 directory with 4โ€ฆ" by @yhakbar in #6433

    Original source
  • Jun 26, 2026
    • Date parsed from source:
      Jun 26, 2026
    • First seen by Releasebot:
      Jun 16, 2026
    • Modified by Releasebot:
      Sep 24, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.0-rc3

    Terragrunt ships v1.1.0 release candidate with six features now enabled by default, including stack dependencies, CAS, redesigned catalog, better reading detection, opt-out auth, and a dependency tree run queue, plus bug fixes and release attestation verification.

    v1.1.0 Release Candidate

    This is the third release candidate for Terragrunt v1.1.
    It carries the same six completed experiments as v1.1.0-rc2, plus bug fixes for those experiments and improvements to how releases are published and verified.

    This release completes the following experiments:

    • stack-dependencies
    • cas
    • catalog-redesign
    • mark-many-as-read
    • opt-out-auth
    • dag-queue-display

    Future release candidates for v1.1.0 will include bug fixes related to these experiments or other urgent bug fixes as necessary, and documentation improvements.

    Please try out this release candidate in lower environments and share your feedback in the associated GitHub discussion.

    ๐Ÿ†• Changes since rc2

    ๐Ÿ› Bug Fixes

    Dependency output resolution: Resolving a dependency block's outputs now applies the env_vars from the unit's extra_arguments blocks whose commands include output (#6396).

    Stack autoinclude: Transitive autoinclude dependencies that point at a stack directory now resolve correctly (#6389).

    Scaffold variable detection: terragrunt scaffold now reads input variables from the module directory only, so variable blocks in nested modules or examples no longer leak into the scaffolded inputs (#6381).

    ๐Ÿ’ก Tips Added

    Nested stack generation: When a non-glob | type=stack filter generates a stack but leaves its nested stacks ungenerated, terragrunt stack generate now prints a tip showing how to generate them too (#6387).

    ๐Ÿ“š Documentation

    New FAQ (#6378) and Terragrunt Patterns (#6379) sections are now available at docs.terragrunt.com.

    โœจ New Features

    Stack dependencies

    A stack generates a tree of units from a single terragrunt.stack.hcl file. Wiring one of those units to another used to mean defining dependency blocks in your catalog and threading dependency paths through values. Stack dependencies let you declare those relationships up front instead.

    Add an autoinclude block inside a unit or stack block, and Terragrunt generates a partial configuration (a terragrunt.autoinclude.hcl file) next to the generated terragrunt.hcl or terragrunt.stack.hcl that's automatically merged into the unit or stack definition. The new unit.<name>.path and stack.<name>.path references resolve to generated paths, so you don't have to hardcode them:

    # terragrunt.stack.hcl
    unit "vpc" {
      source = "github.com/acme/catalog//units/vpc"
      path   = "vpc"
    }
    
    unit "app" {
      source = "github.com/acme/catalog//units/app"
      path   = "app"
    
      autoinclude {
        dependency "vpc" {
          config_path = unit.vpc.path
        }
    
        inputs = {
          vpc_id = dependency.vpc.outputs.vpc_id
        }
      }
    }
    

    Anything that's valid in a unit configuration is valid in its autoinclude block, so you can also patch catalog units with configuration they don't ship with, like retry rules:

    # terragrunt.stack.hcl
    unit "app" {
      source = "github.com/acme/catalog//units/app"
      path   = "app"
    
      autoinclude {
        errors {
          retry "transient_errors" {
            retryable_errors = [".*Error: transient network issue.*"]
            max_attempts     = 3
            sleep_interval_sec = 5
          }
        }
      }
    }
    

    The same works for nested stacks: an autoinclude block inside a stack block patches the generated terragrunt.stack.hcl, so you can, for example, add an extra unit to one environment without forking the stack in your catalog.

    Stack configurations also gained two capabilities along the way:

    • include blocks now work in terragrunt.stack.hcl files, so shared stack configuration can live in a parent folder.
    • dependency blocks can target stack directories, and the run queue expands them to the units inside. Note that this relationship only goes one way: units can depend on stacks, but stacks cannot depend on stacks or units.

    See the stacks documentation for the full reference. Previously gated behind the stack-dependencies experiment, all of this is now enabled by default.

    Content Addressable Store (CAS)

    The Content Addressable Store (CAS) deduplicates source downloads across configurations. It addresses repositories and modules by their content, stores them locally, and serves later requests from that local store instead of repeating the fetch. This speeds up catalog cloning, OpenTofu/Terraform source fetching, and stack generation, and identical files occupy disk space once regardless of how many configurations use them.

    The CAS is no longer limited to Git. It also deduplicates HTTP, Amazon S3, Google Cloud Storage, Mercurial, and SMB sources, along with OpenTofu/Terraform registry sources fetched via tfr://. See supported sources for how each one resolves and deduplicates content.

    CAS is enabled by default. Use the --no-cas flag (or TG_NO_CAS=true) to opt out of it for a run:

    terragrunt run --all --no-cas -- plan
    

    Two new attributes give you finer control, and both default to off:

    update_source_with_cas makes a generated stack self-contained. Set it on a unit, stack, or terraform block with a relative source, and terragrunt stack generate rewrites that source into a content-addressed cas:: reference, so the generated tree no longer depends on the surrounding repository layout. Catalog authors can keep relative paths in their sources and still ship a portable, reproducible stack:

    # stacks/networking/terragrunt.stack.hcl
    unit "vpc" {
      source = "../..//units/vpc"
      path   = "vpc"
      update_source_with_cas = true
    }
    

    After terragrunt stack generate, the relative path is replaced by a reference to the exact tree the CAS stored:

    # Generated output
    unit "vpc" {
      source = "cas::sha1:f39ea0ebf891c9954c89d07b73b487ff938ef08b"
      path   = "vpc"
      update_source_with_cas = true
    }
    

    mutable controls how the CAS places fetched content on disk. By default, the CAS hard links files from its shared store into .terragrunt-cache and marks them read-only, which is fast and uses no extra space, but means the files can't be edited in place. Set mutable = true on a terraform block to copy the content instead, making the working tree safe to edit at the cost of extra I/O and disk space:

    # units/vpc/terragrunt.hcl
    terraform {
      source  = "github.com/acme/catalog//modules/vpc"
      mutable = true
    }
    

    Previously gated behind the cas experiment, the CAS no longer requires --experiment cas.

    Redesigned terragrunt catalog

    The catalog command has been redesigned. It now starts without any configuration, discovers components across your catalog repositories in the background, and streams them into the TUI as they're found.

    Discovery is no longer limited to a modules/ directory; components can live anywhere in a catalog repository. To control what gets discovered, add a .terragrunt-catalog-ignore file with .gitignore-style globs for the paths you want filtered out.

    Components in the TUI now carry metadata to help you navigate a large catalog: each one shows a kind label (template, stack, unit, or module) and optional tags defined in the front-matter of its README.md. From the component list, press s to open a new screen that interactively collects the values used to scaffold the component into your repository.

    Previously gated behind the catalog-redesign experiment, the redesigned catalog is now the default terragrunt catalog experience.

    Reading detection for local module sources

    Terragrunt can select units by the files they read, which is the basis of change-based runs in CI. Previously, pointing a unit's terraform block at a local directory didn't mark the files inside that directory as read, so a change to the module wouldn't select the unit.

    When a unit's source is a local module, Terragrunt now records the module's *.tf, *.tf.json, *.hcl, *.tofu, and *.tofu.json files as read by that unit, so --filter 'reading=<path>' and --queue-include-units-reading select the unit when a module file changes:

    terragrunt run --all --filter 'reading=./modules/vpc/main.tf' -- plan
    

    For files that reading detection doesn't track on its own, the new mark_glob_as_read() HCL function expands a glob and marks every matching file as read in one call:

    locals {
      configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
    }
    

    Existing pipelines built on --queue-include-units-reading or reading= filters may select more units than before, because changes to local module files now count as reads. Previously gated behind the mark-many-as-read experiment, these behaviors no longer require --experiment mark-many-as-read.

    Skip auth during discovery with --no-discovery-auth-provider-cmd

    By default, Terragrunt runs your --auth-provider-cmd once for every unit it discovers, so HCL functions that need credentials resolve correctly during parsing. In a large repository, that can mean hundreds of invocations before any unit runs, which can dominate wall-clock time on change-based runs.

    The --no-discovery-auth-provider-cmd flag (env: TG_NO_DISCOVERY_AUTH_PROVIDER_CMD) skips those invocations during the discovery phase, leaving auth to run only for the units that actually execute:

    terragrunt run --all \
      --no-discovery-auth-provider-cmd \
      --queue-include-units-reading=./changed-file.txt \
      -- plan
    

    Warning

    Use this only when you know parsing resolves without credentials. Units whose configuration depends on values from --auth-provider-cmd during discovery (for example, via get_aws_account_id()) will fail to parse when the flag is set.

    Previously gated behind the opt-out-auth experiment, the flag now works without --experiment opt-out-auth.

    Run queue displayed as a dependency tree

    Before a run --all, Terragrunt lists the units it's about to run. That list now renders as a dependency tree by default instead of a flat list, with units nested under their dependencies, so the run order and the relationships between units are visible before anything executes:

    The following units will be run, starting with dependencies and then their dependents:

    .
    โ”œโ”€โ”€ monitoring
    โ•ฐโ”€โ”€ vpc
    โ•ฐโ”€โ”€ database
    โ•ฐโ”€โ”€ backend-app
    

    The header adapts to direction: dependencies come before dependents on apply, and the order reverses on destroy.

    Previously gated behind the dag-queue-display experiment, the tree display no longer requires --experiment dag-queue-display.

    ๐Ÿ’ก Tips Added

    Tip when filtering a stack leaves nested stacks ungenerated

    terragrunt stack generate --filter './my-stack | type=stack' generates only the selected
    stack, not the nested stacks it contains, which can be surprising for a stack of stacks.

    When a non-glob | type=stack filter leaves a stack's nested stacks ungenerated, Terragrunt
    now prints a tip showing how to generate them too, for example
    --filter './my-stack | type=stack' --filter './my-stack/** | type=stack'.

    ๐Ÿ› Bug Fixes

    Fix permission denied when generated files overwrite CAS-materialized files

    With the CAS enabled, Terragrunt fetches sources as read-only files. Writing a generated file over one of them no longer fails with permission denied:

    • Files from generate blocks with if_exists = "overwrite", when the module ships the target file (for example, its own versions.tf).
    • terragrunt.values.hcl, when the unit or stack source already contains one.
    • terragrunt.autoinclude.hcl, when the unit or stack source already contains one.
    • .terraform.lock.hcl, when the provider cache server updates a committed lock file during init -upgrade.

    In each case, the read-only file is replaced with a writable one, and the shared CAS store is never modified.

    Reject update_source_with_cas on a terraform block when CAS is disabled

    terragrunt stack generate --no-cas now fails when a generated unit's terraform block sets update_source_with_cas = true, instead of silently emitting the unit with its relative source unchanged. The relative source has no meaning once CAS is disabled, so the generated unit could not resolve its module. This matches the existing behavior for the same attribute on unit and stack blocks, and for a run invoked with --no-cas.

    Apply extra_arguments env vars when resolving dependency outputs

    Resolving a dependency block's outputs now applies the env_vars from the unit's terraform extra_arguments blocks whose commands include output.

    Select units reading added or deleted glob files in Git-based filters

    Git-based filters (for example terragrunt run --all --filter '[HEAD^1...HEAD]' -- plan) now select units
    that read an added or deleted file through mark_glob_as_read, even when that file lives outside the unit's
    own directory. Previously only modified files outside a unit reached those units; adding or deleting a file
    the glob matched left the reading unit out of the run, so its real config change was skipped. Added files are
    matched against the newer reference, and deleted files against the older one where the file still exists.

    mark_glob_as_read constrains its walk to a boundary

    mark_glob_as_read now confines glob expansion to a boundary directory. By default the boundary is the enclosing Git repository root; outside a Git repository it is unset. A pattern whose walk would begin outside the boundary returns an error instead of expanding.

    This bounds patterns that resolve higher than intended. For example, "${local.dir}/{.yaml}" becomes /{.yaml} when local.dir is empty, which previously walked the entire filesystem. A ? : conditional does not prevent this, because HCL evaluates both branches of a conditional before selecting one. Wrapping the call in try lets the error fall back to a default:

    locals {
      files = sort(try(mark_glob_as_read("${local.dir}/{*.yaml,*.yml,*.json}"), []))
    }
    

    Pass a leading --terragrunt-boundary argument to set the boundary explicitly, for example to scope the walk to a subdirectory or to widen it to the filesystem root:

    locals {
      scoped = mark_glob_as_read("--terragrunt-boundary=/etc/terragrunt", "/etc/terragrunt/{*.yaml}")
      all = mark_glob_as_read("--terragrunt-boundary=/", "/{*.yaml}")
    }
    

    Scaffold only detects variables in the module directory

    terragrunt scaffold now reads input variables from the module directory itself, matching what OpenTofu and Terraform load for a root module. Previously it scanned subdirectories too, so variable blocks defined in nested modules or examples leaked into the scaffolded inputs even though the module never exposes them.

    Resolve interpolated object keys in autoinclude blocks

    terragrunt stack generate now resolves interpolated object keys in autoinclude blocks (for example
    { "${local.prefix}_key" = ... }), even when the value references dependency.*. Previously the generated
    unit kept the key verbatim, leaking a stack-only reference that is not valid in the unit scope.

    Fix panic on non-string literal interpolation in autoinclude templates

    terragrunt stack generate no longer panics when an autoinclude template interpolates a non-string literal (for example "${0}" or "${true}") alongside a dependency.* reference. The interpolated literal is now rendered to its string form (${0} becomes 0) and the dependency reference is preserved for the unit.

    Resolve transitive autoinclude dependencies on a stack directory

    run --all no longer fails with "does not contain a terragrunt.hcl file" when an autoinclude dependency points at a stack directory (one holding terragrunt.stack.hcl) and the unit is reached transitively through another unit. The dependency cycle check now skips a target with no unit config, matching the direct dependency case.

    ๐Ÿงช Experiments Updated

    Six experiments completed

    The following experiments graduated to general availability in this release, and the features they gated are now enabled by default:

    • stack-dependencies
    • cas
    • catalog-redesign
    • mark-many-as-read
    • opt-out-auth
    • dag-queue-display

    Each feature is described in the New Features section above.

    The corresponding --experiment flags (and TG_EXPERIMENT values) are no longer needed. Passing one still works, but emits a warning about the completed experiment, so you can drop it at your convenience.

    Thank you to everyone who ran these experiments early and filed the feedback that got them here.

    โš™๏ธ Process Updates

    Immutable releases

    Starting with this release, Terragrunt releases are published as immutable releases on GitHub. Once a release is published, its tag and assets can no longer be modified or deleted, so the binary you download is guaranteed to be the same binary that was uploaded when the release was published.

    See the releases process documentation for details, and Verifying releases with the GitHub CLI for how to check a download against the release attestation.

    Install script verifies release attestations

    The install script now checks downloaded release assets against the release attestation that ships with immutable releases. For releases starting with v1.1.0, when an authenticated GitHub CLI (v2.81.0 or later) is available, the script verifies the checksums file and the binary against the attestation before installing, and aborts if either does not match the published release. The check is skipped with a warning when gh is unavailable, too old, or unauthenticated. Use --no-verify-attestation to opt out.

    Pull Requests

    โœจ Features

    • feat: Tip how to run a stack's units when a stack filter matches no units by @denis256 in #6387

    ๐Ÿ› Bug Fixes

    • fix: autoincludes variables interpolation by @denis256 in #6318
    • fix: generate overwrite of read-only CAS-materialized files by @denis256 in #6329
    • fix: CAS integration with committed module lockfiles by @yhakbar in #6330
    • fix: improved resolving of complex objects in keys by @denis256 in #6317
    • fix: Fixing update_source_with_cas integration with --no-cas by @yhakbar in #6363
    • fix: Adding support for adding/deleting files in Git diffs by @yhakbar in #6352
    • fix: Adding --terragrunt-boundary to mark_glob_as_read by @yhakbar in #6351
    • fix: Only detect variables in root directory of module by @yhakbar in #6381
    • fix: resolve transitive autoinclude dependency on a stack directory by @denis256 in #6389
    • fix: apply extra_arguments env_vars when resolving dependency outputs by @denis256 in #6396
    • fix: Fixing indenter style by @yhakbar in #6402

    ๐Ÿ“– Documentation

    • docs: Documenting --parallelism tweaking considerations better by @yhakbar in #6313
    • docs: Adding TGS 'Terragrunt at scale' page by @yhakbar in #6307
    • docs: v1.1.0 changelog polish by @yhakbar in #6333
    • docs: Improving performance docs by @yhakbar in #6332
    • docs: Adding immutable releases docs by @yhakbar in #6337
    • docs: Cleaning up 1.1.0 docs by @yhakbar in #6339
    • docs: Updating provider size claims for provider cache server docs by @yhakbar in #6341
    • docs: Documenting Discovery as a term by @yhakbar in #6359
    • docs: Cleaning up catalog tabs docs by @yhakbar in #6369

    ๐Ÿงน Chores

    • chore: marking as completed stack dependencies experiment by @denis256 in #6249
    • chore: Completing mark-many-as-read experiment by @yhakbar in #6310
    • chore: Completing cas experiment by @yhakbar in #6254
    • chore: Addressing feedback from #6254 by @yhakbar in #6324
    • chore: Completing dag-queue-display experiment by @yhakbar in #6320
    • chore: Completing opt-out-auth experiment by @yhakbar in #6321
    • chore: Dropping go-git by @yhakbar in #6325
    • chore: Addressing PR #6325 feedback by @yhakbar in #6335
    • chore: bump cicd to use opentofu 1.12.2 by @denis256 in #6343
    • chore: Signing GHA update by @denis256 in #6340
    • chore: multiple dependencies update by @denis256 in #6356
    • chore: Updating CI w/ Terragrunt guide to have more accurate screenshots by @yhakbar in #6357
    • chore: drop usage of github.com/NYTimes/gziphandler by @denis256 in #6364
    • chore(deps): bump astro from 6.3.2 to 6.4.6 in /docs by @dependabot[bot] in #6366
    • chore: Completing catalog-redesign experiment by @yhakbar in #6271
    • chore: Bumping JS dependencies by @yhakbar in #6368
    • chore: Adding release attestation verification to install script by @yhakbar in #6344
    • chore: Addressing weekly test stats by @yhakbar in #6354
    • chore: Addressing #6351 feedback by @yhakbar in #6373
    • chore: Adding mise.toml lockfile by @yhakbar in #6372
    • chore: Making progress on lll by @yhakbar in #6377
    • chore: autoinclude fuzzing tests improvements by @denis256 in #6342
    • chore: Expand pure testing through venv by @yhakbar in #6090
    • chore: Dropping TestWindowsTflintIsInvoked by @yhakbar in #6382
    • chore: Continuing with progress on lll #2 by @yhakbar in #6385
    • chore: added CICD guard for detecting not run tests by @denis256 in #6383
    • chore: Addressing weekly tests stats (2026-06-22) by @yhakbar in #6390
    • chore: Addressing #6390 feedback by @yhakbar in #6391
    • chore: go deps update by @denis256 in #6392
    • chore: Running go fix ./... by @yhakbar in #6398
    • chore(deps): bump actions/checkout from 6.0.2 to 7.0.0 by @dependabot[bot] in #6394
    • chore: Update cfg locking for units by @yhakbar in #6401
    • chore: Move log flags off writers by @yhakbar in #6403
    • chore: Adding env and writers to venv by @yhakbar in #6404

    ๐Ÿ“ Other Changes

    • Adding Terragrunt Patterns section by @karlcarstensen in #6379
    • Add FAQ section for docs.terragrunt.com by @karlcarstensen in #6378
    • Fixing codespell lint error and adding codespell lint commands by @karlcarstensen in #6386
    Original source
  • Jun 12, 2026
    • Date parsed from source:
      Jun 12, 2026
    • First seen by Releasebot:
      Jul 1, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.1.0-rc1

    Terragrunt releases v1.1.0 release candidate with graduated stack dependencies, content addressable store, redesigned catalog, improved change detection, an opt-out for discovery auth, and a dependency-tree run queue, plus bug fixes and docs updates.

    v1.1.0 Release Candidate

    This is the first release candidate for Terragrunt v1.1.

    This release completes the following experiments:

    • stack-dependencies
    • cas
    • catalog-redesign
    • mark-many-as-read
    • opt-out-auth
    • dag-queue-display

    Future release candidates for v1.1.0 will include bug fixes related to these experiments or other urgent bug fixes as necessary, and documentation improvements.

    Please try out this release candidate in lower environments and share your feedback in the Associated GitHub discussion.

    โœจ New Features

    Stack dependencies

    A stack generates a tree of units from a single terragrunt.stack.hcl file. Wiring one of those units to another used to mean defining dependency blocks in your catalog and threading dependency paths through values. Stack dependencies let you declare those relationships up front instead.

    Add an autoinclude block inside a unit or stack block, and Terragrunt generates a partial configuration (a terragrunt.autoinclude.hcl file) next to the generated terragrunt.hcl or terragrunt.stack.hcl that's automatically merged into the unit or stack definition. The new unit.<name>.path and stack.<name>.path references resolve to generated paths, so you don't have to hardcode them:

    # terragrunt.stack.hcl
    unit "vpc" {
    source = "github.com/acme/catalog//units/vpc"
    path = "vpc"
    }
    unit "app" {
    source = "github.com/acme/catalog//units/app"
    path = "app"
    autoinclude {
    dependency "vpc" {
    config_path = unit.vpc.path
    }
    inputs = {
    vpc_id = dependency.vpc.outputs.vpc_id
    }
    }
    }
    

    Anything that's valid in a unit configuration is valid in its autoinclude block, so you can also patch catalog units with configuration they don't ship with, like retry rules:

    # terragrunt.stack.hcl
    unit "app" {
    source = "github.com/acme/catalog//units/app"
    path = "app"
    autoinclude {
    errors {
    retry "transient_errors" {
    retryable_errors = [".*Error: transient network issue.*"]
    max_attempts = 3
    sleep_interval_sec = 5
    }
    }
    }
    }
    

    The same works for nested stacks: an autoinclude block inside a stack block patches the generated terragrunt.stack.hcl, so you can, for example, add an extra unit to one environment without forking the stack in your catalog.

    Stack configurations also gained two capabilities along the way:

    include blocks now work in terragrunt.stack.hcl files, so shared stack configuration can live in a parent folder.

    dependency blocks can target stack directories, and the run queue expands them to the units inside. Note that this relationship only goes one way: units can depend on stacks, but stacks cannot depend on stacks or units.

    See the stacks documentation for the full reference. Previously gated behind the stack-dependencies experiment, all of this is now enabled by default.

    Content Addressable Store (CAS)

    The Content Addressable Store (CAS) deduplicates source downloads across configurations. It addresses repositories and modules by their content, stores them locally, and serves later requests from that local store instead of repeating the fetch. This speeds up catalog cloning, OpenTofu/Terraform source fetching, and stack generation, and identical files occupy disk space once regardless of how many configurations use them.

    The CAS is no longer limited to Git. It also deduplicates HTTP, Amazon S3, Google Cloud Storage, Mercurial, and SMB sources, along with OpenTofu/Terraform registry sources fetched via tfr://. See supported sources for how each one resolves and deduplicates content.

    CAS is enabled by default. Use the --no-cas flag (or TG_NO_CAS=true) to opt out of it for a run:

    terragrunt run --all --no-cas -- plan
    

    Two new attributes give you finer control, and both default to off:

    update_source_with_cas makes a generated stack self-contained. Set it on a unit, stack, or terraform block with a relative source, and terragrunt stack generate rewrites that source into a content-addressed cas:: reference, so the generated tree no longer depends on the surrounding repository layout. Catalog authors can keep relative paths in their sources and still ship a portable, reproducible stack:

    # stacks/networking/terragrunt.stack.hcl
    unit "vpc" {
    source = "../..//units/vpc"
    path = "vpc"
    update_source_with_cas = true
    }
    

    After terragrunt stack generate, the relative path is replaced by a reference to the exact tree the CAS stored:

    # Generated output
    unit "vpc" {
    source = "cas::sha1:f39ea0ebf891c9954c89d07b73b487ff938ef08b"
    path = "vpc"
    update_source_with_cas = true
    }
    

    mutable controls how the CAS places fetched content on disk. By default, the CAS hard links files from its shared store into .terragrunt-cache and marks them read-only, which is fast and uses no extra space, but means the files can't be edited in place. Set mutable = true on a terraform block to copy the content instead, making the working tree safe to edit at the cost of extra I/O and disk space:

    # units/vpc/terragrunt.hcl
    terraform {
    source = "github.com/acme/catalog//modules/vpc"
    mutable = true
    }
    

    Previously gated behind the cas experiment, the CAS no longer requires --experiment cas.

    Redesigned terragrunt catalog

    The catalog command has been redesigned. It now starts without any configuration, discovers components across your catalog repositories in the background, and streams them into the TUI as they're found.

    Discovery is no longer limited to a modules/ directory; components can live anywhere in a catalog repository. To control what gets discovered, add a .terragrunt-catalog-ignore file with .gitignore-style globs for the paths you want filtered out.

    Components in the TUI now carry metadata to help you navigate a large catalog: each one shows a kind label (template, stack, unit, or module) and optional tags defined in the front-matter of its README.md. From the component list, press s to open a new screen that interactively collects the values used to scaffold the component into your repository.

    Previously gated behind the catalog-redesign experiment, the redesigned catalog is now the default terragrunt catalog experience.

    Reading detection for local module sources

    Terragrunt can select units by the files they read, which is the basis of change-based runs in CI. Previously, pointing a unit's terraform block at a local directory didn't mark the files inside that directory as read, so a change to the module wouldn't select the unit.

    When a unit's source is a local module, Terragrunt now records the module's *.tf, *.tf.json, *.hcl, *.tofu, and *.tofu.json files as read by that unit, so --filter 'reading=<path>' and --queue-include-units-reading select the unit when a module file changes:

    terragrunt run --all --filter 'reading=./modules/vpc/main.tf' -- plan
    

    For files that reading detection doesn't track on its own, the new mark_glob_as_read() HCL function expands a glob and marks every matching file as read in one call:

    locals {
    configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
    }
    

    Existing pipelines built on --queue-include-units-reading or reading= filters may select more units than before, because changes to local module files now count as reads. Previously gated behind the mark-many-as-read experiment, these behaviors no longer require --experiment mark-many-as-read.

    Skip auth during discovery with --no-discovery-auth-provider-cmd

    By default, Terragrunt runs your --auth-provider-cmd once for every unit it discovers, so HCL functions that need credentials resolve correctly during parsing. In a large repository, that can mean hundreds of invocations before any unit runs, which can dominate wall-clock time on change-based runs.

    The --no-discovery-auth-provider-cmd flag (env: TG_NO_DISCOVERY_AUTH_PROVIDER_CMD) skips those invocations during the discovery phase, leaving auth to run only for the units that actually execute:

    terragrunt run --all \
    --no-discovery-auth-provider-cmd \
    --queue-include-units-reading=./changed-file.txt \
    -- plan
    

    Warning

    Use this only when you know parsing resolves without credentials. Units whose configuration depends on values from --auth-provider-cmd during discovery (for example, via get_aws_account_id()) will fail to parse when the flag is set.

    Previously gated behind the opt-out-auth experiment, the flag now works without --experiment opt-out-auth.

    Run queue displayed as a dependency tree

    Before a run --all, Terragrunt lists the units it's about to run. That list now renders as a dependency tree by default instead of a flat list, with units nested under their dependencies, so the run order and the relationships between units are visible before anything executes:

    The following units will be run, starting with dependencies and then their dependents:

    .
    โ”œโ”€โ”€ monitoring
    โ•ฐโ”€โ”€ vpc
    โ•ฐโ”€โ”€ database
    โ•ฐโ”€โ”€ backend-app
    

    The header adapts to direction: dependencies come before dependents on apply, and the order reverses on destroy.

    Previously gated behind the dag-queue-display experiment, the tree display no longer requires --experiment dag-queue-display.

    ๐Ÿ› Bug Fixes

    Fix permission denied when generated files overwrite CAS-materialized files

    With the CAS enabled, Terragrunt fetches sources as read-only files. Writing a generated file over one of them no longer fails with permission denied:

    Files from generate blocks with if_exists = "overwrite", when the module ships the target file (for example, its own versions.tf).

    terragrunt.values.hcl, when the unit or stack source already contains one.

    terragrunt.autoinclude.hcl, when the unit or stack source already contains one.

    .terraform.lock.hcl, when the provider cache server updates a committed lock file during init -upgrade.

    In each case, the read-only file is replaced with a writable one, and the shared CAS store is never modified.

    Resolve interpolated object keys in autoinclude blocks

    terragrunt stack generate now resolves interpolated object keys in autoinclude blocks (for example

    { "${local.prefix}_key" = ... }), even when the value references dependency.*. Previously the generated

    unit kept the key verbatim, leaking a stack-only reference that is not valid in the unit scope.

    Fix panic on non-string literal interpolation in autoinclude templates

    terragrunt stack generate no longer panics when an autoinclude template interpolates a non-string literal (for example "${0}" or "${true}") alongside a dependency.* reference. The interpolated literal is now rendered to its string form (${0} becomes 0) and the dependency reference is preserved for the unit.

    ๐Ÿงช Experiments Updated

    Six experiments completed

    The following experiments graduated to general availability in this release, and the features they gated are now enabled by default:

    • stack-dependencies
    • cas
    • catalog-redesign
    • mark-many-as-read
    • opt-out-auth
    • dag-queue-display

    Each feature is described in the New Features section above.

    The corresponding --experiment flags (and TG_EXPERIMENT values) are no longer needed. Passing one still works, but emits a warning about the completed experiment, so you can drop it at your convenience.

    Thank you to everyone who ran these experiments early and filed the feedback that got them here.

    Pull Requests

    ๐Ÿ› Bug Fixes

    • fix: autoincludes variables interpolation by @denis256 in #6318
    • fix: generate overwrite of read-only CAS-materialized files by @denis256 in #6329
    • fix: CAS integration with committed module lockfiles by @yhakbar in #6330
    • fix: improved resolving of complex objects in keys by @denis256 in #6317

    ๐Ÿ“– Documentation

    • docs: Documenting --parallelism tweaking considerations better by @yhakbar in #6313
    • docs: Adding TGS 'Terragrunt at scale' page by @yhakbar in #6307
    • docs: v1.1.0 changelog polish by @yhakbar in #6333
    • docs: Improving performance docs by @yhakbar in #6332

    ๐Ÿงน Chores

    • chore: marking as completed stack dependencies experiment by @denis256 in #6249
    • chore: Completing mark-many-as-read experiment by @yhakbar in #6310
    • chore: Completing cas experiment by @yhakbar in #6254
    • chore: Addressing feedback from #6254 by @yhakbar in #6324
    • chore: Completing dag-queue-display experiment by @yhakbar in #6320
    • chore: Completing opt-out-auth experiment by @yhakbar in #6321
    • chore: Dropping go-git by @yhakbar in #6325
    • chore: Addressing PR #6325 feedback by @yhakbar in #6335
    Original source
  • Jun 10, 2026
    • Date parsed from source:
      Jun 10, 2026
    • First seen by Releasebot:
      Jun 11, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.0.8

    Terragrunt releases a broad update with faster file tracking, sharper error messages, and expanded support across CAS, catalogs, autoinclude, and S3 sources. It also adds new experiments for hooks and telemetry, plus documentation improvements including clean Markdown for every docs page.

    ๐ŸŽ๏ธ Performance Improvements

    Faster read-file tracking with the mark-many-as-read experiment

    With the mark-many-as-read experiment enabled, Terragrunt records every module file it marks as read during parsing. The bookkeeping for that record scaled quadratically: each new path was checked against every path recorded so far, which got expensive for units with large local module sources, and monorepos paid that cost again for every unit and every command.

    Recording a path now takes constant time no matter how many paths came before it, and re-marking already-recorded files is cheaper still. The reading lists reported by find and list are unchanged.

    ๐Ÿ› Bug Fixes

    assume_role: preserve commas inside list expressions

    Terragrunt previously failed to correctly parse assume_role attributes containing list values such as transitive_tag_keys or policy_arns. Commas inside nested list expressions were incorrectly treated as top-level separators, causing generated configurations to fail with parsing errors.

    assume_role = {
    role_arn = "arn:aws:iam::123456789012:role/test-role"
    transitive_tag_keys = ["Project", "Projects"]
    }
    

    This resulted in errors similar to:

    Missing item separator; Expected a comma to mark the beginning of the next item.

    Terragrunt now preserves commas inside nested list and object expressions when parsing assume_role blocks, allowing configurations containing array attributes to be processed correctly.

    Thanks to @Rahul-Kumar-prog for contributing this fix!

    Completed experiments now evaluate as permanently enabled

    Features gated behind a completed experiment were treated as disabled instead of permanently enabled, so functionality that graduated out of experiment status could silently stop working.

    The one affected code path was hcl validate --inputs with a git filter expression such as --filter '[HEAD~1...HEAD]': after the filter-flag experiment completed, the command stopped preparing git worktrees for the filter. Git filter expressions now work with hcl validate --inputs again, matching find, list, and the other commands that accept filters.

    Exposed-include resolution errors now name the include block, file, and failing field

    When resolving an include block with expose = true, Terragrunt surfaced low-level parsing or conversion errors with no indication of which include block, file, or field was at fault. This was especially hard to debug for errors that carry no source location, such as:

    unsuitable value: a bool is required

    The error is now annotated with the include block name, the included (parent) file path, and a single dotted locator for the failing field โ€” the top-level config field (dependency, inputs, locals, or feature) plus the attribute path within it when go-cty can determine one:

    exposed include "root" (/path/to/root.hcl): dependency.outputs["enabled"]: unsuitable value: a bool is required

    When go-cty cannot resolve a precise attribute path, the locator degrades to just the field name:

    exposed include "root" (/path/to/root.hcl): dependency: unsuitable value: a bool is required

    Errors that originate in HCL parsing already carry a source range (file:line:column) and are preserved unchanged. This narrows the search from the entire configuration tree to a specific file and field.

    Intersecting a graph traversal with another filter no longer drops the traversed components

    A graph traversal combined with an intersected filter dropped the components reached in discovery.

    e.g., ...a-dependent | type=unit (the dependents of a-dependent, intersected with type of units) returned only a-dependent itself instead of its dependents, and git-change traversals such as ...[HEAD~1...HEAD] | type=unit lost the dependents of the changed units.

    A component that matched both a graph expression target and a positive filesystem or git filter was classified as discovered before the graph traversal ran, so the traversal never expanded from it. Terragrunt now checks graph expression targets first, so intersecting a traversal with another filter keeps the dependencies and dependents it reaches.

    generate blocks now honor hcl_fmt

    Terragrunt now accepts hcl_fmt on generate blocks and preserves the setting when configurations are parsed, written, and parsed again. This lets generated .tf, .hcl, and .tofu files opt out of automatic HCL formatting by setting hcl_fmt = false, matching the existing generate = { ... } attribute-map behavior.

    Telemetry resource now honors OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES

    Terragrunt previously hardcoded the service.name resource attribute to terragrunt for every emitted trace and metric, ignoring the standard OpenTelemetry environment variables. Multiple Terragrunt invocations could not be distinguished in an OpenTelemetry backend without an intermediate collector to rewrite the attribute.

    The resource is now composed via resource.New with WithFromEnv() placed after Terragrunt's defaults, so OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES are honored on every span and metric. Per the OpenTelemetry specification, OTEL_SERVICE_NAME takes precedence over a service.name entry in OTEL_RESOURCE_ATTRIBUTES. The default service.name remains terragrunt when neither variable is set.

    s3:: sources: support virtual-hosted-style URLs

    s3:: source URLs using the virtual-hosted-style S3 endpoint format were rejected:

    terraform {
    source = "s3::https://my-bucket.s3.us-west-2.amazonaws.com/terraform/modules/myapp.zip"
    }
    

    This resulted in errors like:

    ERROR downloading source url s3::https://my-bucket.s3.us-west-2.amazonaws.com/...

    • URL is not a valid S3 URL

    Terragrunt now accepts every AWS S3 endpoint form, including virtual-hosted-style URLs (<bucket>.s3.<region>.amazonaws.com) and modern path-style URLs (s3.<region>.amazonaws.com).

    Windows console mode is restored when Terragrunt exits

    On Windows, running a Terragrunt command from Nushell could leave the shell unable to read input afterward, with keystrokes such as the arrow keys appearing as raw escape sequences instead of being interpreted.

    While it runs, Terragrunt reconfigures the console it shares with the parent shell so that terminal escape sequences are processed, but it did not put the original mode back when it exited. PowerShell reapplies its own console settings on every prompt and recovers on its own, so the problem surfaces only in shells that keep the inherited mode, such as Nushell. Terragrunt now records the console mode at startup and restores it on exit, returning the shell to the state it was in beforehand.

    Reported in #6245.

    ๐Ÿ“– Documentation Updates

    Clean Markdown is available for every docs page at <url>.md

    Every docs page is now served as clean Markdown at the same URL with .md appended. For example, /getting-started/install is also available at /getting-started/install.md.

    curl https://docs.terragrunt.com/getting-started/install.md
    

    The .md version contains the page content without the site navigation or other surrounding HTML, which makes it well suited as context for LLMs and AI tooling: it is smaller and carries only the documentation itself. Coverage includes every page, including the CLI command reference and the changelog.

    This complements the existing llms.txt and llms-full.txt files by providing a per-page Markdown source.

    ๐Ÿงช Experiments Added

    optional-hooks โ€” Add experimental --no-hooks flag support for terragrunt run

    The terragrunt run command now supports an experimental --no-hooks flag for disabling hook execution during command runs.

    The feature is gated behind the optional-hooks experiment and skips execution of before_hook, after_hook, and error_hook blocks when enabled.

    TG_EXPERIMENT=optional-hooks terragrunt run --no-hooks plan
    

    This feature is currently experimental because disabling hooks changes Terragrunt execution semantics and may evolve in future releases.

    Using --no-hooks without enabling the optional-hooks experiment will return an error.

    hook-context-env experiment exposes additional TG_CTX_* env vars to hooks

    Enable the new hook-context-env experiment to surface three additional environment variables to every before_hook, after_hook, and error_hook:

    TG_CTX_HOOK_TYPE โ€” before_hook, after_hook, or error_hook, identifying which lifecycle phase invoked the hook.
    TG_CTX_SOURCE โ€” the resolved terraform source URL (CLI --source override, else evaluated terraform.source with source-map applied, else .).
    TG_CTX_TERRAGRUNT_DIR โ€” the directory of the current Terragrunt config.

    terragrunt run --all --experiment hook-context-env -- apply
    

    These variables make it easier to share a single hook script across lifecycle phases and to access the unit's source and config directory without threading them through hook arguments.

    ๐Ÿงช Experiments Updated

    cas: fallbacks now emit telemetry

    When the cas experiment is enabled and a CAS operation cannot complete, Terragrunt falls back to a slower path (the standard download client, or a temporary clone when the shared git store is unavailable) and keeps going. Until now the only record of a fallback was a warning in the logs, which made it impractical to measure how often CAS degrades across a fleet.

    Each fallback now also emits a cas_fallback telemetry event whose reason attribute identifies the cause: init_error, getter_error, git_store_unavailable, probe_failure, or stack_generation_error. Operators collecting OpenTelemetry traces or metrics from Terragrunt can count and alert on these events to judge CAS health before relying on it by default.

    CAS flags for the catalog command

    The catalog command now accepts the --no-cas and --cas-clone-depth flags, which were already available on run, stack generate, and stack run. When --no-cas is set, catalog repositories are cloned with plain Git even if the cas experiment is enabled. --cas-clone-depth controls the git clone --depth value the CAS uses when cloning catalog repositories.

    terragrunt catalog --experiment cas --cas-clone-depth=-1
    

    cas โ€” update_source_with_cas requires a literal source string

    When a catalog unit, stack, or terraform block set update_source_with_cas = true with a source that was not a literal string, rewriting silently produced a wrong source. Interpolation such as "../units/${local.name}" had the interpolated portion dropped, leaving a bare prefix; a reference such as local.foo resolved to the directory containing the block itself. In both cases stack generation packaged the wrong directory without any error.

    Stack generation now fails with an error explaining that update_source_with_cas requires a literal source string. Non-literal expressions, including interpolation, function calls, and references like local.foo, are rejected.

    cas โ€” Malformed cas:: references fail with a clear error

    A cas:: source with a malformed hash, such as cas::sha1:a, used to fail with an opaque internal error while looking the hash up in the store.

    CAS references are now validated up front: the hash must be lowercase hexadecimal with exactly 40 characters for sha1 or 64 for sha256. References that don't match are rejected with an error identifying the bad reference.

    cas โ€” Repositories with submodules now clone correctly

    Cloning a repository that contains git submodules through the Content Addressable Store failed while ingesting the repository:

    git_cat_file: fatal: Not a valid object name <hash>

    A submodule appears in the repository tree as a pointer to a commit in another repository, so the object behind it cannot be read from the repository being cloned.

    The CAS now fetches each submodule from the URL registered in .gitmodules at its pinned commit and materializes its contents in place, including nested submodules. Relative submodule URLs (such as ../sibling.git) are resolved against the parent repository URL, matching git's behavior. Submodule contents are stored and deduplicated like any other content, so repeated clones reuse the cache.

    catalog-redesign โ€” Failures now exit nonzero and name the sources that failed

    The redesigned catalog exited with code 0 even when it failed: a session that ended on an unreachable repository, a failed scaffold, or a failed copy reported success in its exit code. Repositories that failed to load during discovery were dropped too: the warning logged for each one was drawn over by the full-screen interface, so a run where every source failed showed the same "No catalog sources were discovered" screen as a run that genuinely found nothing.

    The catalog now exits nonzero when the session ends on a failure: a discovery failure that leaves nothing to browse, a failed scaffold, or a failed copy. Quitting a working session still exits 0. When some sources fail to load while others succeed, the catalog stays usable and a clean quit still exits 0; the component list shows how many sources failed, and the failed repositories are printed with their causes after the catalog closes. When every source fails, the error screen lists each failed repository instead of claiming nothing was found, and dismissing it exits nonzero.

    Running terragrunt catalog without an interactive terminal, such as in CI, used to fail with a raw error from the underlying TUI library:

    bubbletea: error opening TTY: bubbletea: could not open TTY: open /dev/tty: no such device or address
    

    It now fails immediately with an error stating that the catalog command requires an interactive terminal.

    catalog-redesign โ€” Scaffolding a component no longer fails with a path-traversal error

    Scaffolding a component from the catalog (pressing s) could fail on macOS while downloading the source:

    subdirectory component contain path traversal out of the repository
    

    The catalog caches each repository under the system temporary directory, which macOS reports through a symlink (/var/folders/... pointing at /private/var/folders/...). The source location Terragrunt handed to the downloader was built against the unresolved path, so it pointed outside the cached repository and was rejected.

    Terragrunt now resolves the temporary directory before discovering components, so the source stays inside the repository and scaffolding proceeds.

    stack-dependencies: HCL tooling now handles autoinclude

    Two tooling gaps around the experimental autoinclude block are closed:

    hcl validate now validates autoinclude blocks. With the stack-dependencies experiment enabled, validating a terragrunt.stack.hcl that declares autoinclude runs the same strict checks as terragrunt stack generate. A malformed block (for example, a locals block inside autoinclude) is now reported at validation time instead of passing hcl validate and only failing later during generation. Without the experiment, validation behavior is unchanged.

    read_terragrunt_config() can read stack-level autoinclude files. Reading a generated terragrunt.autoinclude.stack.hcl previously failed because the file was decoded as a unit configuration, which rejects its unit and stack blocks. With the experiment enabled, the file is now decoded as the stack-file fragment it is, returning its unit and stack blocks the same way reading a terragrunt.stack.hcl does. Unit-level terragrunt.autoinclude.hcl files already read correctly and continue to do so.

    stack-dependencies: autoinclude merges like a regular include

    A generated unit autoinclude (terragrunt.autoinclude.hcl) now merges into the unit's config using the same default merge as a regular include, which is a shallow merge, applied uniformly across generation, full parse, and discovery. Top-level keys from the unit and the autoinclude combine, and on a conflict the autoinclude wins and replaces the unit's value rather than deep-merging nested maps; locals stay local in scope.

    A generated stack autoinclude (terragrunt.autoinclude.stack.hcl) injects unit and stack blocks into the generated terragrunt.stack.hcl. An injected block whose name matches an existing unit or stack now overrides that block wholesale, consistent with unit autoinclude override semantics, and an injected block with a new name is added. This applies uniformly across generation, full parse, and discovery, so a name match no longer produces a duplicate-name error. A stack autoinclude may not declare a top-level dependency block (stacks have no dependencies; declare the dependency inside the target unit's own autoinclude).

    A dependency block injected through an autoinclude is now available before a unit's remote_state is evaluated, so referencing dependency.<name>.outputs.<key> there no longer fails. remote_state now behaves the same as generate blocks.

    stack-dependencies: autoinclude blocks can reference values.*

    An autoinclude block may now reference the stack's values.. Previously a values. reference was rejected at stack generate time, except in a dependency config_path. It now resolves to a literal like local.*, unit.<name>.path, and stack.<name>.path, wherever it appears: inputs, generate, remote_state, mock_outputs, and config_path.

    Function calls in an autoinclude now resolve at generate time too, in the terragrunt.stack.hcl context, instead of being kept verbatim and evaluated in the generated unit. Only a dependency.* reference (a dependency's outputs) stays verbatim and resolves inside the unit; in a mixed expression the stack-level parts resolve and only the dependency.* reference is kept.

    Because functions now evaluate against the stack file rather than the unit, directory and include functions report the stack file's location: get_terragrunt_dir returns the stack file's directory, and path_relative_to_include returns ".". If you relied on these resolving in the unit, move them to the unit's own configuration, or derive a per-unit value such as a remote_state backend key from unit.<name>.path.

    A locals block inside an autoinclude remains rejected; declare stack-level locals in terragrunt.stack.hcl instead.

    stack-dependencies: stack dependencies resolve values.* in the target stack's locals

    Expanding a dependency that points at a generated stack directory no longer fails when that stack's terragrunt.stack.hcl reads values.* in its locals block. Previously, terragrunt stack generate succeeded but terragrunt run --all then failed with There is no variable named "values" while expanding the dependency into its units.

    Dependency expansion now reads the generated terragrunt.values.hcl next to each terragrunt.stack.hcl it visits, including nested stacks, so each nesting level resolves values.* from its own values file, the same way a full stack parse does.

    stack-dependencies: component path references in values no longer break next to autoinclude blocks

    A unit or stack block's values can reference unit.<name>.path and stack.<name>.path even when another block in the same terragrunt.stack.hcl declares an autoinclude. Previously, the presence of any autoinclude block made stack generate reject those references with Unknown variable; There is no variable named "unit", while the same file without an autoinclude generated fine.

    Pull Requests

    โœจ Features

    feat: add hook-context-env experiment by @arnaud-dezandee in #6189

    feat: Adding CAS fallback telemetry by @yhakbar in #6298

    ๐Ÿ› Bug Fixes

    fix: Fixing filter tests on Windows by @yhakbar in #6247

    fix: Fix Windows nushell bug by @yhakbar in #6250

    fix(codegen): fix assume_role parsing failure when transitive_tag_keys or policy_arns arrays are present by @Rahul-Kumar-prog in #5975

    fix: Fixing scaffold path traversal check by @yhakbar in #6255

    fix(telemetry): honor OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES environment variables by @Tensho in #6256

    fix: Fixing graph traversal bug with intersected filter by @yhakbar in #6270

    fix: resolve locals in autoinclude mock_outputs by @denis256 in #6274

    fix: report the offending field in Terragrunt config errors by @denis256 in #6284

    fix: Fixing values without autoinclude by @yhakbar in #6290

    fix: Fixing values references in locals of terragrunt.stack.hcl files by @yhakbar in #6291

    fix: Fixing experiment promotion by @yhakbar in #6293

    fix: Adding support for submodules in CAS by @yhakbar in #6294

    fix: Validating CAS sources by @yhakbar in #6296

    fix: Support autoinclude in hcl validate and fix read_terragrunt_config() for configurations using autoinclude by @yhakbar in #6297

    fix: Fixing legacy virtual hosted style S3 URLs by @yhakbar in #6311

    fix: Addressing lint findings by @yhakbar in #6312

    ๐Ÿ“– Documentation

    docs: Documenting CAS Getters by @yhakbar in #6251

    docs: Support completedSince by @yhakbar in #6252

    docs: Since/Before cleanup by @yhakbar in #6277

    docs: Since/Before cleanup workflow by @yhakbar in #6276

    docs: Support .md changelog files by @yhakbar in #6286

    docs: Adding autoinclude documentation by @yhakbar in #6299

    ๐Ÿงน Chores

    chore: weekly tests reporting by @denis256 in #6191

    chore: Add thanks for #5975 by @yhakbar in #6253

    chore: autoinclude merge fixes by @denis256 in #6248

    chore: cleaned unused code by @denis256 in #6260

    chore: Add CodeRabbit release check by @yhakbar in #6278

    chore(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 by @dependabot[bot] in #6265

    chore(deps): bump docker/setup-docker-action from 5.1.0 to 5.2.0 by @dependabot[bot] in #6266

    chore(deps): bump aws-actions/configure-aws-credentials by @dependabot[bot] in #6267

    chore: Make autoinclude more flexible, supporting values.* by @yhakbar in #6283

    chore: Threading venv through CLI by @yhakbar in #6089

    chore: Isolate test git servers by @yhakbar in #6233

    chore: Add --no-cas flag to the catalog command by @yhakbar in #6292

    chore: Clean up from #6290 by @yhakbar in #6295

    chore: Adding DAG view tests by @yhakbar in #6300

    chore: Speeding up files read by @yhakbar in #6301

    ๐Ÿ“ Other Changes

    Make guides collapsed to start by @karlcarstensen in #6262

    Fix for light/dark mode by @karlcarstensen in #6264

    Collapse reference section by @karlcarstensen in #6263

    serve clean Markdown at .md for every page by @karlcarstensen in #6281

    Feature/optional hooks experiment by @Rahul-Kumar-prog in #6227

    Update to llms.txt. Added curated llms.txt and configured plugin to also serve full and small by @karlcarstensen in #6280

    Changelog by @karlcarstensen in #6285

    Fix #5054: support hcl_fmt in generate blocks by @DadaVinqi in #6287

    fix Fixing catalog exit codes by @yhakbar in #6302

    Original source
  • Jun 1, 2026
    • Date parsed from source:
      Jun 1, 2026
    • First seen by Releasebot:
      Jun 1, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.0.7

    Terragrunt adds optional versions for tfr:// module sources and expands CAS to cover more remote sources and registry modules. This release also improves stack dependency path handling, fixes Windows filtering and panic cases, and tightens parallelism validation for safer, smoother runs.

    โœจ New Features

    tfr:// source URLs accept an optional version

    The version query parameter on tfr:// source URLs is now optional. When omitted, Terragrunt queries the registry's list-versions endpoint and downloads the latest stable version, matching how OpenTofu and Terraform resolve a module reference that has no version constraint.

    terraform {
      source = "tfr:///terraform-aws-modules/vpc/aws"
    }
    

    Prereleases are excluded from resolution, so a registry that only publishes 4.0.0-rc1 alongside 3.3.0 will pin to 3.3.0. Pin a version explicitly with ?version= when you need reproducible builds or want to opt into a prerelease.

    Thanks to @raman1236 for contributing this feature!

    ๐Ÿ› Bug Fixes

    update_source_with_cas: preserve //subdir on a unit's terraform.source

    When a unit's terraform.source used the // subdir convention (for example, source = "../..//modules/foo") and opted into update_source_with_cas, the rewritten source dropped the //subdir tail and the synthetic tree contained only the leaf module's files. A module that referenced a sibling via a relative path (source = "../bar") could not resolve that reference after materialization.

    Rewrites now preserve the original //subdir (for example, cas::sha1:<hash>//modules/foo), and the synthetic tree is rooted at the path before //, so sibling files reachable via relative paths land in the materialized working directory.

    Sources without // are unchanged: the tree stays scoped to the leaf module, and the rewritten reference has no //subdir tail.

    --filter now detects affected units on Windows

    On Windows, terragrunt find --filter '[origin/main...HEAD]' (and its variants) returned no affected units even when git diff reported changed files. The source= and reading= filters were affected by the same problem.

    Filter glob patterns are always written with forward slashes, but the affected-unit comparison was being made with Windows backslash separators, so nothing matched. Terragrunt now compares paths consistently with forward slashes on every platform, and the filter detects changed units on Windows as it already did on Linux and macOS.

    Reported in #6214.

    startswith, endswith, strcontains, and run_cmd no longer panic on malformed calls

    Calling startswith, endswith, or strcontains with the wrong number of arguments (for example a single argument instead of two) crashed Terragrunt instead of reporting a configuration error. Calling run_cmd with only option flags and no command (for example run_cmd("--terragrunt-quiet")) crashed the same way.

    These calls now return a clear error: a wrong-number-of-parameters error for the string functions, and an empty-command error for run_cmd.

    The --parallelism flag no longer accepts non-positive numbers

    Previously, terragrunt commands that accept the --parallelism flag (or equivalently the $TG_PARALLELISM environment variable) used to hang indefinitely when invoked with --parallelism=0.

    Terragrunt now validates that the value is positive and exits with an error otherwise.

    Reported in #6211

    ๐Ÿงช Experiments Updated

    cas โ€” content-addressing for non-git sources

    CAS now covers module sources beyond git: http(s), Amazon S3, Google Cloud Storage, and Mercurial. Repeat runs against an unchanged remote reuse the cached tree instead of downloading the bytes again.

    Before fetching, CAS issues a cheap remote probe (an HTTP HEAD, an S3 object-attributes lookup, a GCS metadata read, or hg identify) to derive a cache key without pulling the source. On a hit, the cached tree is linked directly; on a miss, or when the remote exposes no usable signal, CAS downloads the source, ingests it, and keys the resulting tree by its content hash. A remote that publishes a new version under the same address pins to a new entry, so a stale cache cannot serve outdated bytes.

    cas โ€” OpenTofu/Terraform registry sources

    Module sources of the form tfr://... are now content-addressed in CAS. Repeat runs against the same pinned registry version reuse the cached module instead of re-downloading the archive from the registry.

    CAS resolves a tfr:// source by asking the registry where the underlying archive lives and uses that resolved URL as the cache key. Two runs that pin the same version share one entry; a republish under the same version pins to a new entry, so a stale cache cannot serve outdated bytes.

    stack-dependencies: unit.<name>.path and stack.<name>.path resolve in values

    The stack-dependencies experiment now exposes unit.<name>.path and stack.<name>.path when evaluating the values attribute of a unit or stack block, not only inside autoinclude blocks. A parent stack can pass the generated path of a sibling component down into a child stack, so a unit nested in that child stack can depend on a unit that lives at a different level of the hierarchy.

    unit "vpc" {
      source = "../catalog/units/vpc"
      path = "vpc"
    }
    
    stack "app" {
      source = "../catalog/stacks/app"
      path = "app"
      values = {
        vpc_path = unit.vpc.path
      }
    }
    

    A unit inside the app stack reads values.vpc_path and uses it as the config_path of an autoinclude dependency, wiring the cross-level relationship at generation time. Paths follow the same layout the generator produces, including no_dot_terragrunt_stack on the referenced block.

    stack-dependencies: simplified unit.* / stack.* ref shape

    The stack-dependencies experiment no longer resolves stack.<name>.<unit_name>.path or stack.<name>.<nested_stack>.path. Only the top-level stack.<name>.path and unit.<name>.path forms remain. stack.<name>.name and unit.<name>.name are gone too; both only ever echoed the label that the reference already had to spell out.

    Nested references required parsing every nested catalog up front and conflicted with the reserved name and path attributes on each ref: a nested unit named name or path could not be addressed.

    To depend on a generated unit inside a stack, compute the path as ${stack.<name>.path}/<unit-relative-path> directly. The layout under a stack's generated directory follows no_dot_terragrunt_stack on the parent stack and on each unit, so hand-computed paths must mirror that resolution.

    stack-dependencies: .terragrunt-stack-origin no longer written

    Terragrunt no longer writes the .terragrunt-stack-origin file when generating nested stacks. Set update_source_with_cas = true on your unit and stack blocks if you would like relative paths in your catalog to resolve correctly instead.

    Pull Requests

    โœจ Features

    feat: Make version optional in tfr:// module registry URLs by @yhakbar in #6112

    feat: Supporting all getters in CAS by @yhakbar in #6076

    ๐Ÿ› Bug Fixes

    fix: Removing use of cas.WithFS by @yhakbar in #6195

    fix: panics in startswith / endswith / strcontains / run_cmd by @denis256 in #5984

    fix: Allowing all TG HCL fns in terragrunt.stack.hcl, including autoinclude by @yhakbar in #6166

    fix: Fixing synthetic trees for terraform.source URLs with // by @yhakbar in #6218

    fix: Fixing nested generation for paths with // by @yhakbar in #6234

    fix: Fixing stack path variables in values by @yhakbar in #6235

    fix: Fixing stack autoinclude by @yhakbar in #6236

    fix: Fixing git filters on Windows by @yhakbar in #6242

    fix: validate that --parallelism value is positive by @ccmtaylor in #6212

    ๐Ÿ“– Documentation

    docs: Cleaning up experiment docs by @yhakbar in #6228

    docs: Fixing unreleased changelog page by @yhakbar in #6237

    docs: Cleaning up changelogs for v1.0.7 by @yhakbar in #6246

    ๐Ÿงน Chores

    chore: Getting rid of fatih/structs dependency by @yhakbar in #6186

    chore: Getting rid of go-homedir direct dependency by @yhakbar in #6184

    chore: improved coderabbit rules by @denis256 in #6193

    chore: Consolidating on lipgloss for color by @yhakbar in #6188

    chore: Removing go-errors as a dependency by @yhakbar in #6182

    chore: tests simplification by @denis256 in #6208

    chore: Adding separate archive field by @yhakbar in #6196

    chore: Addressing UI/UX feedback for interactive scaffold in catalog by @yhakbar in #6175

    chore: added unknown-unknowns heuristics for coderabbit by @denis256 in #6220

    chore: Make clone tests a bit faster by @yhakbar in #6081

    chore: stacks dependencies simplificaitons by @denis256 in #6171

    chore(deps): bump actions/stale from 10.2.0 to 10.3.0 by @dependabot[bot] in #6201

    chore: Fixing gopls workflow by @yhakbar in #6229

    chore: Increasing test coverage for stack dependencies by @yhakbar in #6231

    chore: Go and GHA dependencies by @denis256 in #6240

    chore: Ensuring full support for tfr in CAS by @yhakbar in #6123

    chore: Adding no // path test for source with CAS by @yhakbar in #6239

    chore: Modernizing using gopls by @yhakbar in #6232

    Original source
  • May 25, 2026
    • Date parsed from source:
      May 25, 2026
    • First seen by Releasebot:
      May 26, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.0.6

    Terragrunt ships bug fixes, experiments, and stack improvements, including safer discovery and render handling, an experimental deep_merge HCL function, opt-out auth during discovery, and a more interactive catalog scaffold flow.

    ๐Ÿ› Bug Fixes

    terragrunt no longer hangs when download_dir is a non-hidden subdirectory of the unit

    Setting download_dir (via the attribute, --download-dir, or TG_DOWNLOAD_DIR) to a subdirectory of the unit's working directory whose name did not start with a dot caused commands that prepare the OpenTofu or Terraform source (apply, plan, run, and similar) to hang.

    For example:

    # /infra/web/terragrunt.hcl
    download_dir = "cache"
    terraform {
      source = "./mod"
    }
    

    Here terragrunt apply would copy ./mod into cache/, see the new cache/ directory on the next read of the unit, and recurse into it. The default .terragrunt-cache was unaffected because Terragrunt's source-copy step skips any directory whose name starts with a dot.

    These configurations now produce an immediate error identifying the source and destination paths.

    mark-many-as-read experiment now triggers during discovery

    With the mark-many-as-read experiment enabled, a unit whose terraform { source = ... } pointed at a local module did not show up under --filter 'reading=' filters that referenced files inside that module. Discovery would parse the unit, but the module files were never recorded as read, so the reading filter attribute could not match and the queue came back empty.

    The module walk now runs on the discovery code path as well, so changes to files in a local module source flow through to the units that depend on them.

    terragrunt render no longer crashes on exclude or catalog blocks with certain attributes

    Rendering a config crashed with a value has no attribute of that name panic before any output could be produced when:

    • the exclude block set no_run, or
    • the catalog block set default_template, no_shell, or no_hooks.

    These attributes are now carried through the render pipeline alongside the other fields on their respective blocks, so both blocks round-trip cleanly.

    terragrunt render no longer crashes on multiple errors.ignore blocks with mismatched signals

    Rendering a config that defined more than one errors.ignore block crashed with an inconsistent list element types panic when the signals map was populated on one block and absent (or differently typed) on another. The same crash showed up in dependency-output evaluation, since both paths build the same rendered representation of the config.

    Each ignore block is now rendered with a uniform shape. Indexed access (errors.ignore[0]), length, and iteration still work, and the signals map on each block is preserved as written.

    Suppress spurious Unknown variable: dependency errors during dependency resolution

    terragrunt plan and apply no longer print ERROR Error: Unknown variable "dependency" lines when a unit pulls in a shared include (e.g. via find_in_parent_folders) that references dependency.* outputs. The plans completed correctly, but the error lines cluttered CI logs.

    Resolves #6036.

    terragrunt stack commands no longer crash on stacks with multiple units

    Running terragrunt stack output (or any command that resolves concurrently parsing multiple configuration files) against a stack with several units could intermittently crash while the units were being parsed in parallel due to a race on internal bookkeeping of files read (used in the reading filter attribute).

    Parallel unit parsing now coordinates safely when recording which source files were read, preventing crashes.

    terraform_binary properly respected when both tofu and terraform are on PATH

    A regression in command execution caching resulted in over-caching the STDOUT result of tofu --version when both tofu and terraform were available on PATH and terraform_binary was set. Early on in the execution flow, Terragrunt checks if OpenTofu is installed what its version is to determine if it supports setting of the automatic provider cache directory. This resulted in the value of terraform_binary being ignored for later version checks to assess compliance with terraform_version_constraint.

    The version-detection cache used per run is now scoped to the binary that produced each entry, so the version recorded against an early default-binary resolution no longer leaks into the later resolution that honors terraform_binary.

    ๐Ÿงช Experiments Added

    deep-merge experiment adds a deep_merge HCL function

    Enable the new deep-merge experiment to use the deep_merge(map1, map2, ...) HCL function.

    deep_merge recursively merges map and object values. Later arguments override earlier arguments for overlapping keys, nested maps are merged recursively, lists are appended, and null arguments are ignored.

    This is useful when composing inputs from multiple decoded JSON, YAML, or HCL-derived maps:

    locals {
      config_json_files = sort(fileset(get_terragrunt_dir(), "*.json"))
      config = deep_merge([
        for file in local.config_json_files :
        jsondecode(file("${get_terragrunt_dir()}/${file}"))
      ]...)
    }
    
    inputs = local.config
    

    Calling deep_merge without enabling the deep-merge experiment returns an error.

    opt-out-auth โ€” Opt out of --auth-provider-cmd during discovery

    Enable the new opt-out-auth experiment to use --no-discovery-auth-provider-cmd (env: TG_NO_DISCOVERY_AUTH_PROVIDER_CMD), which disables the auth provider command during the discovery phase.

    Without the flag, Terragrunt assumes that --auth-provider-cmd must be run per parsed component during the discovery phase so that it can reliably resolve HCL functions such as get_aws_account_id and run_cmd. On large repositories with run --all --filter='reading=', this dominates wall-clock time because the auth command runs for every discovered unit rather than only the subset that will run.

    The --no-discovery-auth-provider-cmd flag turns off auth invocations during discovery. The auth provider command still runs normally when running units.

    Units whose discovery-relevant blocks depend on credentials produced by --auth-provider-cmd will fail to parse with the flag set. Use it when you know that parsing will resolve successfully without any authentication done beforehand by Terragrunt.

    While this flag is experimental, you must also opt-in to the opt-out-auth experiment by setting the TG_EXPERIMENT environment variable to opt-out-auth or by passing the --experiment=opt-out-auth flag to terragrunt run. This flag might experience breaking changes based on community feedback for the duration of the experiment.

    e.g.

    terragrunt run --all \
      --experiment=opt-out-auth \
      --no-discovery-auth-provider-cmd \
      --queue-include-units-reading=./changed-file.txt \
      plan
    

    ๐Ÿงช Experiments Updated

    catalog-redesign โ€” Interactive scaffold form on s

    Pressing s from the catalog list or detail view now opens an in-TUI form that prompts for every variable/value the selected component exposes. The form is modal: in navigate mode j and k (or the arrow keys) move between fields and enter interacts with the focused one. Required entries are flagged, and optional entries show their default in a muted style until the user opts in.

    enter on a text or HCL field switches the form into edit mode. Typing edits the value in place; esc returns to navigate. Only fields the user actually changes get written to the generated file, and optional defaults stay implicit, so the result is leaner than the placeholder flow.

    enter on a boolean field toggles between [x] true and [ ] false directly, without a separate edit mode.

    x on an optional field marks it "use default" again, removing any in-progress value and leaving the source's default to apply.

    Complex types (lists, maps, objects) accept raw HCL and are validated before the file is written, so a typo surfaces inline rather than producing a broken terragrunt.hcl or terragrunt.values.hcl file.

    ctrl+d finishes the form. Required fields the user never set still write as # TODO: fill in value so the rest of the file is usable.

    S (capital) keeps the previous placeholder-only flow, generating the same TODO-laden file as before for users who prefer to populate values by editing the generated file.

    stack-dependencies: parser tolerates HCL expressions throughout terragrunt.stack.hcl

    The stack-dependencies experiment now defers evaluation of source, path, values, and include.path until each unit or stack block is parsed on its own. As a result, autoinclude resolution during stack generation and run --all discovery no longer fall over when other parts of a stack file use Terragrunt functions, local., or values.. A few adjacent behaviors are tightened up at the same time.

    Autoinclude resolves even when sibling units use expressions.

    Before 1.0.6, if any unit in a stack file used a function call or a local.* / values.* reference in source, path, or values, generating an autoinclude on a different unit in the same file could fail. The parser now leaves those expressions alone until they're needed, so an unrelated unit can carry an autoinclude block without being blocked by its neighbors:

    locals {
      shared_region = "us-east-1"
    }
    
    unit "account" {
      source = "${get_terragrunt_dir()}/../catalog/units/account"
      path = "account"
      values = {
        account = values.account
        region = local.shared_region
      }
    }
    
    unit "roles" {
      source = "${get_terragrunt_dir()}/../catalog/units/roles"
      path = "roles"
      autoinclude {
        dependency "account" {
          config_path = unit.account.path
        }
      }
    }
    

    include blocks in terragrunt.stack.hcl accept computed paths.

    The path attribute on an include block can be an HCL expression, not just a string literal. An autoinclude block in the included file is resolved normally after the include merges in:

    include "shared" {
      path = find_in_parent_folders("shared.stack.hcl")
    }
    

    What's Changed

    • chore: Fixing release bug fix notification by @yhakbar in #6143
    • fix: Fixing panic in render WriteTo by @yhakbar in #6144
    • fix: Fixing files read race by @yhakbar in #6145
    • Implement IndexNow by @karlcarstensen in #6150
    • docs: added filtering of compatibility table by @denis256 in #6149
    • feat: add deep_merge HCL function as experiment by @EvansM4 in #5535
    • chore(deps): bump jdx/mise-action from 4.0.0 to 4.0.1 by @dependabot[bot] in #6153
    • chore(deps): bump peter-evans/create-pull-request from 8.1.0 to 8.1.1 by @dependabot[bot] in #6154
    • chore(deps): bump DavidAnson/markdownlint-cli2-action from 22.0.0 to 23.2.0 by @dependabot[bot] in #6155
    • chore(deps): bump the js-dependencies group across 1 directory with 12 updates by @dependabot[bot] in #6156
    • chore: Trimming JS deps by @yhakbar in #6159
    • feat: Allow opt-out from auth in discovery by @yhakbar in #6119
    • chore: Removing setup-go from Windows signing by @yhakbar in #6158
    • chore: stacks dependencies variables by @denis256 in #6072
    • chore: google dependencies update by @denis256 in #6161
    • docs: Cleaning up changelog for stack dependencies permissive parser by @yhakbar in #6163
    • fix: Fixing tf binary version over-caching by @yhakbar in #6148
    • chore: Addressing feedback from #6148 by @yhakbar in #6168
    • fix: Fixing panic in ignore signal by @yhakbar in #6167
    • feat: Add TUI interactivity for catalog scaffold by @yhakbar in #6162
    • chore: Plumbing in vfs to getter by @yhakbar in #6070
    • fix: Reducing spurious unknown variable dependency errors by @yhakbar in #6060
    • chore: Upping strict control test coverage by @yhakbar in #6080
    • chore: Increasing virtualization further by @yhakbar in #6084
    • docs: Enhance documentation for --out-dir usage by @FernandoArteaga-telus in #6176
    • fix: Fixing mark-many-as-read for discovery by @yhakbar in #6174
    • chore: Bumping golang deps from #6164 by @yhakbar in #6177
    • chore: Clean-up of comments from #6174 by @yhakbar in #6179
    • chore: Clean up strict control debug message by @yhakbar in #6178
    • chore: Removing go-commons dependency by @yhakbar in #6180
    • fix: Fixing download_dir copy infinite recursion by @yhakbar in #6169
    • chore: Fixing sops tests by @yhakbar in #6187

    New Contributors

    • @EvansM4 made their first contribution in #5535
    • @FernandoArteaga-telus made their first contribution in #6176

    Full Changelog: v1.0.5...v1.0.6

    Original source
  • May 18, 2026
    • Date parsed from source:
      May 18, 2026
    • First seen by Releasebot:
      May 19, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.0.5

    Terragrunt releases faster stack output, full cross-platform provider lock files, and a broad set of fixes for auth, caching, source handling, and run reporting. It also adds new CAS enhancements, an azure-backend experiment, and better filter and stack-target guidance.

    โœจ New Features

    Full .terraform.lock.hcl files from the provider cache server

    When the provider cache server is used against the OpenTofu provider registry, Terragrunt now writes .terraform.lock.hcl files containing h1: hashes for every platform the registry supports. A single terragrunt init produces a lock file that works on every platform, removing the need to run tofu providers lock -platform=... separately for each target architecture.

    provider "registry.opentofu.org/hashicorp/null" {
      version = "3.2.2"
      constraints = "3.2.2"
      hashes = [
        "h1:+1mRmfyz6oA00IhrrSkHK3h/Mdh032x2p0F6OMdMo5s=",
        "h1:FjLTqvaaYo+vHN8pHZB1cOwEGiNzOj+I9kQyHmr9/7o=",
        # ... one entry per supported platform ...
        "zh:00e5877d19fb1c1d8c4b3536334a46a5c86f57146fd115c7b7b4b5d2bf2de86d",
        # ... one entry per supported platform ...
      ]
    }
    

    The hashes come from the registry's per-platform download response. When the registry does not supply them (for example, a third-party registry that has not adopted the field), Terragrunt falls back to its previous behavior of writing an h1: hash for the current platform plus zh: hashes for every platform listed in the shasums document.

    Tip

    Thanks to the OpenTofu team

    This feature builds on work done by the OpenTofu team to expose per-platform hashes directly from the OpenTofu provider registry. Starting with OpenTofu 1.12, tofu init populates .terraform.lock.hcl with hashes for every supported platform out of the box, with no tofu providers lock invocation required. Users on older OpenTofu versions still get the same lock files when running through Terragrunt's provider cache server, but upgrading to 1.12 is the easiest way to get the same behavior everywhere, including when using the automatic provider cache dir.

    ๐ŸŽ๏ธ Performance Improvements

    stack output fetches unit outputs in parallel

    terragrunt stack output now fetches outputs from multiple units at the same time, which is noticeably faster on larger stacks. Use the existing --parallelism flag (or TG_PARALLELISM) to lower concurrency if you need to.

    terragrunt stack output --parallelism 4
    

    ๐Ÿ’ก Tips Added

    Stack-target hint when --filter is missing | type=stack

    run and stack generate now emit a tip when a --filter path resolves to a directory containing terragrunt.stack.hcl but the filter is not restricted to stacks. Without | type=stack, stack generate ignores the filter and run does not generate that stack.

    The tip prints the offending filter, the suggested rewrite, and a link to the filter docs. Suppress it with --no-tip stack-target-missing-type-stack or --no-tips.

    ๐Ÿ› Bug Fixes

    Auth provider command returning null no longer crashes Terragrunt

    If the command configured via --auth-provider-cmd wrote the JSON value null to stdout, Terragrunt crashed with a nil pointer dereference before it could obtain credentials.

    A null response is now treated as an empty response: no environment variables and no credentials are applied, and the run continues.

    Auto-init now re-runs after a source change when modules are already cached

    terragrunt plan/apply could fail with Error: Required plugins are not installed after a source-version change in any unit with a module "" block. The .terragrunt-init-required marker written on source change was being ignored because modulesNeedInit short-circuited as soon as .terraform/modules/ existed.

    The marker check now lives at the top of needsInitRunCfg and is honored regardless of cached .terraform/modules/ contents.

    Thanks to @arnaud-dezandee for contributing this fix!

    --download-dir is now respected through dependency blocks and read_terragrunt_config

    A custom download directory set via --download-dir (or TG_DOWNLOAD_DIR) was honored for the unit being run, but lost as soon as parsing crossed into another config. dependency blocks and read_terragrunt_config() would fall back to the dependency's local .terragrunt-cache next to its terragrunt.hcl, ignoring the user-set path.

    TG_DOWNLOAD_DIR=/tmp/tg-cache terragrunt run --all plan
    # Root unit: outputs landed in /tmp/tg-cache โœ“
    # Dependency outputs: written next to each dependency's terragrunt.hcl โœ—
    

    When the parsing context switches to a new config path, the download directory is now updated only if it still points at the previous module's default location. A user-supplied path never matches any module's default and is carried through every dependency hop unchanged.

    Thanks to @maonat for contributing this fix!

    remote_state โ€” apply tags during DynamoDB lock table creation

    Terragrunt previously applied dynamodb_table_tags to DynamoDB lock tables after table creation rather than during the initial CreateTable API request.

    This caused failures in environments enforcing required AWS resource tags through SCPs or tag policies, where tags must be present at resource creation time.

    Terragrunt now includes dynamodb_table_tags in the initial DynamoDB table creation request during remote state bootstrap.

    remote_state {
      backend = "s3"
      config = {
        bucket = "my-state-bucket"
        dynamodb_table = "terraform-locks"
        dynamodb_table_tags = {
          Environment = "prod"
          Team = "platform"
        }
      }
    }
    

    Thanks to @Rahul-Kumar-prog for contributing this fix!

    Engine archive extraction rejects path-traversal entries

    When Terragrunt extracted an engine archive while the engine experiment was active, entries whose target path resolved outside the extraction directory were not rejected correctly. Such an entry could overwrite files anywhere the Terragrunt process could write.

    These entries are now rejected early with a descriptive error before any bytes are written. Engine archives produced by Gruntwork were never affected; the gap only mattered for a tampered or untrusted archive.

    Thanks to @jackiesre721 for reporting this!

    --filter combined with a negation no longer parses excluded units

    When a positive path filter was combined with a negated one, Terragrunt classified any unit that matched neither expression as requiring defensive parsing before exclusion instead of being excluded early.

    e.g.

    $ terragrunt run --all --filter './foo' --filter '!./baz'
    # If `./bar` existed on disk, it would be parsed before being excluded. This is no longer the case.
    

    Any positive filepath filter now consistently results in units that cannot be discovered during Terragrunt discovery being excluded from parsing for evaluating candidacy of inclusion. When a sufficiently complex filter is present, like the following:

    $ terragrunt run --all --filter './foo' --filter '!./baz' --filter 'reading=root.hcl'
    # If `./bar` existed on disk, it will still be parsed before being excluded to determine if it reads `root.hcl`.
    

    macOS and Windows binaries report the correct release version

    The v1.0.4 macOS and Windows release binaries reported terragrunt version main and parsed as 0.0.0, breaking any terragrunt_version_constraint configured against them.

    sign-macos.yml and sign-windows.yml included build jobs for standalone workflow_dispatch runs. During the release workflow, those jobs still saw the original workflow_dispatch event from release.yml, so the old condition evaluated to true. The redundant build used BUILD_VERSION=${{ github.ref_name }} and replaced the correctly versioned artifact uploaded by build.yml.

    The signing workflows now skip their internal build job when invoked via workflow_call and only build when dispatched directly, so release binaries keep the version stamped by build.yml.

    Fixed 403 Forbidden on nested private modules when using the provider cache server

    With TG_PROVIDER_CACHE enabled, OpenTofu/Terraform sent nested module-registry lookups to the upstream registry with the cache server's API key as the bearer token, instead of the credentials configured for that host. Private registries rejected those requests:

    Error: Error accessing remote module registry
    Failed to retrieve available versions for module "&lt;name&gt;" from
    &lt;registry&gt;: error looking up module versions: 403 Forbidden.
    

    Terragrunt sets TF_TOKEN_<host> to the cache server's API key so the cache can front provider downloads. Module-registry requests bypassed the cache and went straight to the upstream, so the registry saw the cache key instead of the user's token.

    The cache server now also fronts the modules.v1 endpoint for each configured registry. It drops the inbound cache-server bearer, looks up the user's credentials for the upstream host from the loaded CLI config (TF_TOKEN_<host>, ~/.terraform.d/credentials.tfrc.json, etc.), and forwards the request with that token.

    Run report file generation no longer stalls or deadlocks with many runs

    Generating a run report via --report-file could stall or deadlock when a queue contained many runs and some were still recording their final status as the report was written.

    Reports now serialize each run independently, so writing a report no longer blocks status updates from runs that are still finishing.

    Thanks to @jackiesre721 for contributing this fix!

    Declining a run --all or --graph confirmation no longer skips cleanup

    When terragrunt run --all destroy (or --all state, --all apply, or the equivalent --graph variants) prompted for confirmation and the user answered "no", Terragrunt terminated the process directly, skipping cleanup the run had registered.

    Cleanup now runs before Terragrunt exits.

    s3:: and gcs:: stack sources now download

    Stack file source URLs starting with s3::https:// or gcs::https:// previously failed with a credentials error even when valid credentials were available. They now download. Existing stack files need no change.

    Plain https://www.googleapis.com/storage/... URLs are now intended to download anonymously without GCP credentials, but Terragrunt continues to use GCS credentials to download them for backward compatibility, emitting a deprecation warning the first time it does so. To opt into the new behavior, enable the legacy-gcs-public-prefix strict control. To pull from a private GCS bucket explicitly, prefix the URL with gcs:: yourself.

    Fixed nested key order in terragrunt stack output

    When a unit lived inside more than one nested stack, terragrunt stack output rendered its key with the stack names reversed, so a unit inside root_stack_3 > stack_v3 > stack_v2 appeared under stack_v2.stack_v3.root_stack_3 instead of root_stack_3.stack_v3.stack_v2. Deeply nested units also leaked to the top level of the output.

    The output now joins stack names from outermost to innermost, matching the declared hierarchy in both the HCL and JSON formats.

    Thanks to @anuragrao04 for contributing this fix!

    Fixed failed to create directory ...: file exists from the provider cache server

    If a previous run had cached a provider by symlinking ~/.terraform.d/plugins/<provider> into Terragrunt's own provider cache, and that user plugin directory was later moved or deleted, the symlink was left dangling. The next run failed with failed to create directory ...: file exists and refused to cache the provider.

    Terragrunt now removes a dangling symlink at the cache path on the next run and proceeds to download the provider. A non-symlink at that path is left in place and surfaced as an error.

    ๐Ÿงช Experiments Added

    azure-backend โ€” Native Azure Storage (azurerm) remote-state support

    The azure-backend experiment has been added as the gate for native Terragrunt support of the Azure Storage (azurerm) remote-state backend. Once it stabilizes, Terragrunt will bootstrap, delete, and migrate Azure storage accounts and blob containers the same way it already does for S3 and GCS, and read state directly from Azure blobs for --dependency-fetch-output-from-state.

    In this release the flag is reserved only. Enabling it has no behavioral effect, and remote_state { backend = "azurerm" } continues to pass through to the OpenTofu and Terraform native azurerm backend.

    Track progress and share feedback in #4307. For setup steps, see the experiment documentation.

    Thanks to @omattsson for driving this experiment forward!

    ๐Ÿงช Experiments Updated

    CAS keeps a central Git store for incremental fetches

    CAS now keeps one bare Git repository per remote URL inside its store, under ~/.cache/terragrunt/cas/store/git/ on Linux by default. See Storage for where this lives on macOS and Windows. On a cache miss, Terragrunt fetches just the requested ref into that repository instead of running a fresh shallow clone into a temporary directory. Repeated misses against the same remote reuse the existing pack files, so fetching a second ref from the same repository transfers only the new objects.

    Concurrent Terragrunt runs against the same remote URL share one fetch instead of cloning in parallel; later runs reuse what the first one transferred. If the shared fetch hangs or fails, Terragrunt logs a warning and falls back to a temporary clone so cloning still succeeds.

    You can reclaim space at any time by deleting the git/ subdirectory:

    rm -rf ~/.cache/terragrunt/cas/store/git
    

    cas โ€” Commit SHAs accepted in ref=

    Source URLs of the form git::<url>?ref=<commit-sha> now resolve through CAS. Previously these clones failed because Terragrunt asked the remote to look up the SHA as a symbolic reference, which Git servers don't support.

    Both full SHAs (SHA-1 and SHA-256) and abbreviated SHAs are accepted. Abbreviated SHAs must disambiguate inside the repository, the same rule Git itself applies.

    terraform {
      source = "git::https://github.com/acme/infrastructure-modules.git//vpc?ref=a1b2c3d4e5f67890abcdef1234567890deadbeef"
    }
    

    The first cold clone of a repository pinned to a commit SHA fetches the full history of every branch. Shallow fetches require a ref name, and fetching a commit SHA at limited depth depends on a server option (uploadpack.allowAnySHA1InWant) that is not universally enabled, so CAS fetches all branches at full depth and resolves the SHA locally. Subsequent clones reuse the cached repository and never touch the network for the same commit. Branch and tag refs continue to use the existing shallow path.

    cas โ€” mutable attribute on terraform, unit, and stack blocks

    A new mutable attribute opts a block out of CAS hardlinking when its source is fetched through CAS. With mutable = true, files materialized into .terragrunt-cache (for terraform) or .terragrunt-stack (for unit and stack) are copied from the CAS store and the working tree is editable.

    The default is false. Files are materialized read-only so an accidental edit cannot reach back into the shared CAS store.

    terraform {
      source = "git::https://github.com/acme/infrastructure-modules.git//vpc?ref=v1.0.0"
      mutable = true
    }
    

    The flag is orthogonal to update_source_with_cas and has no effect when content is fetched through the standard download path, which already produces an independent copy.

    cas โ€” update_source_with_cas now idempotent across unit and stack blocks

    A terragrunt.stack.hcl with two blocks pointing at the same template directory used to fail stack generate when each block had update_source_with_cas = true. The first block's pass rewrote the shared template's source to a cas::sha256:... reference, then the second block's pass re-read the rewritten file and treated the reference as a relative path.

    CAS now skips re-processing a source once it already carries the cas:: prefix, so multiple unit or stack blocks can share a template and resolve to the same synthetic tree.

    cas โ€” symlinks in the source repository

    Source repositories fetched through CAS used to materialize committed symlinks as regular files whose contents were the link target path. The destination tree no longer matched the upstream layout, and any tooling that followed the link saw plain text instead.

    CAS now writes a real symbolic link at the destination. Symlink targets that resolve outside the destination tree are rejected so a hostile or stale source cannot escape the working directory.

    catalog-redesign โ€” component tags

    The catalog-redesign experiment now reads a tags field from the component's README.md front-matter. Tags appear as colored pills next to the component in the list view and in the detail view above the rendered README.

    &lt;!-- Frontmatter
    name: VPC App
    description: A VPC for application workloads.
    tags: [networking, aws, module]
    --&gt;
    

    Either inline-array or dash-list YAML form is accepted. Tags render in gray by default. When a tag matches a known component-type name (module, template, unit, or stack, case-insensitive), the pill takes on that type's color.

    A tag matching a component-type name also promotes the component into that type's tab. A template whose tags include module appears under both Templates (by its native kind) and Modules (by tag), without changing how it scaffolds.

    To learn more, see Component tags.

    What's Changed

    feat: read catalog component tags from README front-matter by @yhakbar in #6033
    feat: Generate full lock files by @yhakbar in #5992
    feat: Adding stack target tip by @yhakbar in #6013
    feat: Adding central Git store for CAS by @yhakbar in #6003
    feat: adding support for commit refs in CAS by @yhakbar in #6010
    feat: Adding mutable attribute for clones to force copy instead of hard links by @yhakbar in #6011
    feat: stack outputs parallel fetching by @denis256 in #6104
    feat: outputs fetching performance improvements by @denis256 in #6122
    feat(experiments): register azure-backend experiment with stub azurerm backend by @omattsson in #6014
    fix: Fixing provider cache test flake by @yhakbar in #6056
    fix: Fixing panic in --auth-provider-cmd against null by @yhakbar in #6137
    fix: Making update_source_with_cas idempotent within a single terragrunt generate call by @yhakbar in #6142
    fix: Refactoring some HCL fn parameter logic by @yhakbar in #6043
    fix: Fixing weak path classifier by @yhakbar in #6062
    fix: Fixing race condition in dependencyBlockToCtyValue by @yhakbar in #6067
    fix: Fixing string coercion in tags for catalog by @yhakbar in #6066
    fix: Propagate original credential configs for requests to modules by @yhakbar in #5999
    fix: Fix missing tags in DynamoDB lock table creation during bootstrap by @Rahul-Kumar-prog in #5974
    fix: Addressing catalog tag feedback by @yhakbar in #6073
    fix: Fixing symlinks in repos for CAS clones by @yhakbar in #6082
    fix: Fixing tag refs in CAS by @yhakbar in #6083
    fix: defer in loop causing lock accumulation in report writer by @jackiesre721 in #6085
    fix: Moving engine extraction to vfs for path traversal check by @yhakbar in #6101
    fix: Fixing CAS symlink bug by @yhakbar in #6102
    fix(auto-init): Re-init on source change when modules are cached by @arnaud-dezandee in #6059
    fix: respect TG_DOWNLOAD_DIR for dependency blocks by @maonat in #6024
    fix: Fixing ordering of nested stacks in terragrunt stack output by @yhakbar in #6113
    fix: Fixing run --all early exit by @yhakbar in #6127
    docs: Update to robots by @karlcarstensen in #6064
    docs: Adding thanks for #5974 by @yhakbar in #6079
    docs: Changelog for #6085 by @yhakbar in #6091
    docs: Adding Continuous Integration w/ Terragrunt guide by @yhakbar in #6006
    docs: Adding redirect to vercel by @karlcarstensen in #6094
    docs: Remove changefreq by @karlcarstensen in #6096
    docs: Vercel trailing slash fix by @karlcarstensen in #6097
    docs: Updating changelog for v1.0.5 by @yhakbar in #6103
    docs: Documenting hook exit code control by @yhakbar in #6106
    docs: Adding thanks for #6059 by @yhakbar in #6107
    docs: Adding changelog entry for #6024 by @yhakbar in #6110
    docs: Adding changelog for #6014 by @yhakbar in #6138
    chore: fixed macos and windows version setting in CICD by @denis256 in #6054
    chore: added handling of draft releases by @denis256 in #6055
    chore: Expanding lll coverage to format-options by @yhakbar in #5868
    chore: Expanding lll coverage to dag-graph by @yhakbar in #5869
    chore: Expanding lll coverage to scaffold by @yhakbar in #5870
    chore: Expanding lll coverage to runcfg by @yhakbar in #5871
    chore: Expanding lll coverage to runall by @yhakbar in #5872
    chore: Expanding lll coverage to telemetry by @yhakbar in #5873
    chore: Expanding lll coverage to options by @yhakbar in #5874
    chore: Expanding lll coverage to retry by @yhakbar in #5877
    chore: Expanding lll coverage to report by @yhakbar in #5880
    chore: Expanding lll coverage to codegen by @yhakbar in #5881
    chore: Addressing feedback from #6025 by @yhakbar in #6031
    chore: Adding TGS --help footer by @yhakbar in #6046
    chore: Adding additional telemetry & logs by @yhakbar in #6057
    chore: Expanding lll coverage to discovery by @yhakbar in #5883
    chore: aws-sdk-go-v2 upgrade by @denis256 in #6065
    chore: Avoid cancelling main workflows by @yhakbar in #6069
    chore: simplified default patterns initialization by @denis256 in #5808
    chore: Adding bug fix release notification workflow by @yhakbar in #6063
    chore: Consolidating go-getter routes by @yhakbar in #6030
    chore: CICD fixes by @denis256 in #6071
    chore(deps): bump astro from 6.1.6 to 6.1.10 in /docs by @dependabot[bot] in #6087
    chore(deps): bump jdx/mise-action from 4.0.0 to 4.0.1 by @dependabot[bot] in #5734
    chore(deps): bump aws-actions/configure-aws-credentials from 6.0.0 to 6.1.0 by @dependabot[bot] in #5812
    chore(deps): bump fast-xml-parser and @aws-sdk/xml-builder in /docs/src/fixtures/terralith-to-terragrunt/app/best-cat by @dependabot[bot] in #5981
    chore(deps): bump fast-xml-builder from 1.1.5 to 1.2.0 in /docs/src/fixtures/terralith-to-terragrunt/app/best-cat by @dependabot[bot] in #6105
    chore(deps): bump actions/cache from 5.0.3 to 5.0.5 by @dependabot[bot] in #5735
    chore(deps): bump sigstore/cosign-installer from 4.0.0 to 4.1.2 by @dependabot[bot] in #5772
    chore: add support for Opentofu 1.12 by @denis256 in #6134

    New Contributors

    @jackiesre721 made their first contribution in #6085

    @arnaud-dezandee made their first contribution in #6059

    @omattsson made their first contribution in #6014

    Full Changelog: v1.0.4...v1.0.5

    Original source
  • May 7, 2026
    • Date parsed from source:
      May 7, 2026
    • First seen by Releasebot:
      May 7, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.0.4

    Terragrunt releases faster command and repo-root caching, smoother fast-copy performance, and broader bug fixes across auth provider calls, include handling, JSON parsing, Windows paths, and the Provider Cache Server. It also expands stack dependencies, CAS, and catalog support.

    ๐ŸŽ๏ธ Performance Improvements

    run_cmd and Git repo-root results memoized without the Provider Cache Server

    Within a single command, repeated run_cmd(...) calls and repeated Git repo-root lookups across units used to share their cached results only when the Provider Cache Server was running. Commands invoked without --provider-cache (the common case for find, list, and run --all against estates that do not need provider caching) re-evaluated each run_cmd and re-shelled to git rev-parse --show-toplevel for every unit.

    Both caches are now active for every command, so identical run_cmd arguments and repeated repo-root lookups are reused across units regardless of whether the Provider Cache Server is enabled.

    fast-copy strict control

    With the new fast-copy strict control enabled, Terragrunt compiles each include_in_copy and exclude_from_copy pattern once and evaluates it inline during a single copy walk. This avoids re-walking subdirectories for every pattern, which should result in noticeable speed improvements for large source modules.

    terragrunt run plan --strict-control fast-copy

    The new matcher does not collapse ** to zero path segments when a neighbor is a wildcard, so a//.tf matches a/sub/main.tf but not a/main.tf. Patterns that relied on the old collapsing behavior should use brace alternation like {.tf,/*.tf} to cover both depths.

    Fewer git rev-parse invocations on large estates

    The get_repo_root() HCL function, the runner, and the find and list discovery commands all ask Git for the enclosing repository root. Previously, two units in the same repository each triggered their own git rev-parse --show-toplevel, even when the answer was identical. On large estates this added up to one fork per unit (and sometimes more) for a value that never changed.

    A repository discovered for one working directory is now reused for any other working directory inside it, for the duration of the command. Nested repositories (a checkout vendored inside another) still resolve to their own root.

    ๐Ÿ› Bug Fixes

    --auth-provider-cmd no longer runs once per dependency cache directory

    Resolving dependency outputs ran the configured --auth-provider-cmd again from inside each .terragrunt-cache working directory, on top of the call already made for the unit.

    Terragrunt now reuses the credentials already obtained for the dependency when reading outputs from a cached working directory, so --auth-provider-cmd is invoked once per dependency instead of twice.

    Fixed exclude block being dropped when defined only in an included parent

    A unit that pulled in an exclude block from an include that did not declare its own exclude block saw the include's exclude configurations ignored.

    Included exclude blocks now get properly merged into unit configurations.

    Reported in #5089. Thanks to @HeikoNeblung for contributing this fix!

    Fixed terragrunt find --include failing on relative include paths

    Running terragrunt find --include against units whose include blocks reference parent configs with relative paths (../root.hcl, ./common.hcl, bare filenames, etc.) emitted errors like Rel: can't make ../root.hcl relative to /abs/working-dir and dropped those entries from the output.

    Relative include paths are now resolved against the unit's directory before being made relative to the working directory, matching how the rest of Terragrunt interprets the path attribute on an include block.

    find and list no longer hard-fail when a path cannot be made relative to its base. The condition is logged as a warning and the path is emitted as-is, so output stays complete and the command exits zero.

    Tolerate non-JSON warnings in tofu/terraform output -json

    Resolving dependency outputs no longer fails when the underlying tofu/terraform output -json invocation prints a deprecation warning to stdout alongside the JSON payload. Terraform 1.15.0 introduced a backend deprecation warning for the S3 dynamodb_table parameter that is emitted on stdout after the JSON object, which broke parsing with errors like invalid character 'W' after top-level value and the misleading downstream message There is no variable named "dependency".

    Terragrunt now isolates the first JSON object in the captured stdout, so leading log lines (for example, the long-standing AWS Client Side Monitoring Enabling CSM line) and trailing warning blocks are both ignored when reading dependency outputs.

    Resolves #6001. Thanks to @jpke for contributing this fix!

    get_repo_root() returns OS-native separators on Windows

    git rev-parse --show-toplevel always emits forward-slash paths, even on Windows. Terragrunt returned that string unchanged from get_repo_root(), so configurations that compared the result against path/filepath-style paths or fed it back into helpers expecting OS-native separators saw spurious mismatches and broken joins on Windows.

    The output is now normalized to OS-native separators before being returned, so get_repo_root() produces C:\repo\path on Windows and /repo/path on Linux and macOS.

    Reported in #5976.

    Hardened module manifest handling

    Terragrunt now bounds .terragrunt-module-manifest cleanup to the manifest's own folder, skips paths with symlinked parents, and removes invalid manifests after reading any valid entries. Existing manifests keep the same gob format.

    Provider Cache Server now supports custom host blocks

    Running terragrunt with the Provider Cache Server enabled against a private registry declared via a host block in .terraformrc (or a file referenced by TF_CLI_CONFIG_FILE) failed with errors such as provider registry.opentofu.org/<org>/<provider> was not found, because the cache server proxy did not recognize the custom registry and rewrote requests to registry.opentofu.org.

    Terragrunt now registers each custom host block with the cache server, seeds its service discovery from the services map so registries that do not serve .well-known/terraform.json still work, and forwards OPENTOFU_NETRC_* / TF_TOKEN_* credentials so authenticated registries continue to authenticate through the proxy.

    # .terraformrc
    host "registry.example.com" {
      services = {
        "providers.v1" = "https://registry.example.com/repository/terraform-hosted/v1/providers/"
      }
    }
    

    Resolves #5916. Thanks to @elkh510 for contributing this fix!

    ๐Ÿงช Experiments Updated

    stack-dependencies โ€” Stricter validation and clearer parse errors for autoinclude

    Malformed configuration inside an autoinclude block previously produced misleading messages or, in some cases, was silently ignored during stack discovery.

    Two changes tighten this up:

    A dependency block inside autoinclude must declare exactly one label. Zero labels (dependency {}) and multiple labels (dependency "a" "b" {}) are now rejected at parse time with a diagnostic that points at the offending block.

    Parse failures encountered while expanding autoinclude files during stack generate, find, and list are surfaced with the underlying HCL diagnostic instead of being swallowed or remapped to a generic discovery error.

    Reported in #5980.

    cas โ€” Local paths supported as stack component sources

    CAS-backed stack generation now accepts a local filesystem path as the source of a consumer stack or unit block, in addition to a remote Git URL. Terragrunt copies the referenced directory into a temporary directory, computes a content-addressed root hash over the copy, and applies the same update_source_with_cas rewriting as the remote flow. The original directory is left untouched.

    # live/terragrunt.stack.hcl
    stack "service" {
      source = "../catalog//stacks/service"
      path = "service"
    }
    

    This makes a catalog usable against a local checkout under the same update_source_with_cas = true attributes that already work for Git URLs, which is helpful when iterating on a catalog before tagging a release.

    See the CAS documentation and Explicit Stacks: Local catalog sources for details.

    catalog-redesign โ€” Units and stacks, scaffolded values, and key-binding cleanup

    The redesigned terragrunt catalog TUI gains two new component kinds, a guided scaffolding flow for placing them, and a small key-binding cleanup.

    Units and stacks join modules and templates

    Catalog discovery now classifies units (directories containing a terragrunt.hcl) and stacks (directories containing a terragrunt.stack.hcl) as first-class component kinds, alongside OpenTofu/Terraform modules and boilerplate templates. The list view picks them up automatically and they appear under their own tabs; press tab and shift+tab to cycle.

    When more than one classification could apply to the same directory (for example, a stack directory that also contains a unit), Terragrunt resolves it to a single kind under a fixed precedence: template, stack, unit, module.

    Copy and scaffolded values

    Selecting a unit or stack from the catalog now offers a copy action that materializes the component into your working directory. Terragrunt walks the copied component for values.<name> references and, if it finds any, writes a sibling terragrunt.values.hcl stub. Names referenced outside a try(...) are listed as required with a "TODO" placeholder; names referenced through a try(...) are listed as optional, pre-populated with the literal default from the fallback. An existing terragrunt.values.hcl is left alone.

    After the TUI exits, Terragrunt prints a short callout pointing at the directory it wrote to and any follow-up command you need to run, instead of leaving you to find the new directory yourself.

    Catalog key bindings

    The ctrl+j binding on the catalog list has been removed in favor of enter alone for choosing a focused entry, and dropped from the navigation set used while filtering. The mini help footer is updated to match.

    stack-dependencies - Separate filenames for unit vs stack autoincludes

    Generated autoinclude files now use distinct filenames depending on the component kind, so tooling (LSP, read_terragrunt_config(), indexers) can identify a file's purpose from its name alone:

    • Unit-level autoincludes continue to be written as terragrunt.autoinclude.hcl.
    • Stack-level autoincludes (autoinclude blocks declared inside a stack { ... }) are now written as terragrunt.autoinclude.stack.hcl. The .stack.hcl suffix mirrors terragrunt.stack.hcl, matching the convention used elsewhere for stack files.
    # terragrunt.stack.hcl
    unit "app" {
      source = "../catalog/units/app"
      path = "app"
      autoinclude {
        # Generated as: .terragrunt-stack/app/terragrunt.autoinclude.hcl
        dependency "vpc" { config_path = unit.vpc.path }
      }
    }
    stack "networking" {
      source = "../catalog/stacks/networking"
      path = "networking"
      autoinclude {
        # Generated as: .terragrunt-stack/networking/terragrunt.autoinclude.stack.hcl
        dependency "shared" { config_path = unit.shared.path }
      }
    }
    

    This change implements the naming convention proposed in the Stack Dependencies RFC so configurations for units and stacks always live in files whose names clearly indicate their purpose.

    To learn more, see the experiment documentation.

    stack-dependencies โ€” Nested stack paths and discovery integration

    The stack-dependencies experiment gains two improvements: nested stack path references at arbitrary depth, and integration with the find and list discovery commands.

    Nested stack path references

    stack.<name>.<nested_stack>.path now resolves at arbitrary nesting depth. Previously, only units within a stack were reachable via stack.<name>.<unit_name>.path; nested stacks are now first-class references too.

    # terragrunt.stack.hcl
    stack "infra" {
      source = "../catalog/stacks/infra"
      path = "infra"
    }
    unit "app" {
      source = "../catalog/units/app"
      path = "app"
      autoinclude {
        dependency "deep" {
          # infra contains a nested "deep" stack; reference it directly.
          config_path = stack.infra.deep.path
        }
        inputs = {
          val = dependency.deep.outputs.val
        }
      }
    }
    

    Discovery commands surface stack dependencies

    The terragrunt find and terragrunt list discovery commands now reflect stack dependencies generated by the autoinclude block. The DAG output correctly orders units by their autoinclude dependencies and shows dependency relationships in JSON, tree, and long formats.

    # JSON output includes dependency relationships from autoinclude
    $ terragrunt find --json --dag --dependencies --experiment stack-dependencies
    # Long list format shows a Dependencies column
    $ terragrunt list --long --dependencies --dag --experiment stack-dependencies
    # Tree format visualizes the dependency hierarchy
    $ terragrunt list --tree --dag --experiment stack-dependencies
    

    Multi-level dependency trees (for example, A โ†’ B,C where B โ†’ D,E) are ordered correctly in DAG mode: leaf units appear first, parents appear after all their dependencies.

    To learn more, see the experiment documentation.

    Cache and plugin directories follow platform conventions

    Terragrunt's global cache directory now resolves to the platform's user cache location instead of a hard-coded ~/.cache/terragrunt. On Linux this honors XDG_CACHE_HOME (still ~/.cache/terragrunt by default), on macOS it resolves to ~/Library/Caches/terragrunt, and on Windows it resolves under %LocalAppData%. The CAS content store, the auto provider cache, and the IaC engine plugin directory all move with it.

    Existing caches at the previous locations are not migrated. They become orphaned and continue to consume disk space until removed.

    Consider deleting the old paths to reclaim that space if you are on macOS or Windows, or have configured a custom XDG_CACHE_HOME:

    # CAS store and engine plugins under the legacy ~/.cache layout
    rm -rf ~/.cache/terragrunt
    

    What's Changed

    feat: stack dependencies in find and dag by @denis256 in #5945
    feat: Adding support for units and stacks in catalog by @yhakbar in #5971
    feat: Adding support for local paths in CAS by @yhakbar in #5933
    feat: updated name for stack depednencies by @denis256 in #6018
    fix: manifest handling improvements by @denis256 in #6032
    fix: Removing extra --auth-provider-cmd call by @yhakbar in #6045
    fix: Use FromSlash on return of git rev-parse --show-toplevel by @yhakbar in #5987
    fix: Cleaning up #5232 by @yhakbar in #6009
    fix: better errors reporting form autoincludes by @denis256 in #5985
    fix: Dropping references to ctrl-+j in the catalog key bindings by @yhakbar in #6007
    fix: Ensuring relativization is safer by @yhakbar in #6025
    fix: tolerate non-JSON warnings in tofu/terraform output -json (#6001) by @jpke in #6029
    fix: support custom host blocks in Provider Cache Server by @elkh510 in #5917
    perf: Memoizing get_repo_root() better by @yhakbar in #5989
    perf: Moving context cache construction earlier by @yhakbar in #6019
    docs: Adding a Pull Requests section to the changelog by @yhakbar in #5982
    docs: Update buttons on nav by @karlcarstensen in #6000
    docs: Calling out update for existing cache locations in v1.0.4 by @yhakbar in #6005
    docs: Cleaning up changelog for v1.0.4 by @yhakbar in #6050
    docs: Documenting #5917 by @yhakbar in #6044
    docs: Optimizing SEO a bit by @yhakbar in #5990
    docs: Updating terminology to modernize it a bit by @yhakbar in #6016
    chore: Supporting immutable releases by @yhakbar in #5905
    chore: Cleaning up other scripts with shellcheck and shfmt by @yhakbar in #5983
    chore: Add fast-copy strict control by @yhakbar in #5966
    chore: Expanding lll coverage to worktrees by @yhakbar in #5861
    chore: Expanding lll coverage to os-exec by @yhakbar in #5862
    chore: Expanding lll coverage to runner-creds by @yhakbar in #5865
    chore: Expanding lll coverage to tflint by @yhakbar in #5866
    chore: Expanding lll coverage to queue by @yhakbar in #5867
    chore: Addressing review feedback on #5989 by @yhakbar in #5991
    chore: Integrate vexec into engine by @yhakbar in #5957
    chore: Moving to XDG-aware paths by @yhakbar in #5941
    chore: Optimizing catalog performance by @yhakbar in #5973
    chore: Adding checkbox for changelog updates by @yhakbar in #6012
    chore: Integrating vexec into Command by @yhakbar in #6004
    chore: Addressing #6019 feedback by @yhakbar in #6023
    chore: Addressing test flakes by @yhakbar in #6028
    chore: Adding thank you to @jpke for fix in #6029 by @yhakbar in #6035
    chore: Dropping insignificant OpenTelemetry traces by @yhakbar in #6034
    chore: Adding better symlinks experiment tests by @yhakbar in #6038

    New Contributors

    @jpke made their first contribution in #6029
    @elkh510 made their first contribution in #5917

    Full Changelog: v1.0.3...v1.0.4

    Original source
  • Apr 27, 2026
    • Date parsed from source:
      Apr 27, 2026
    • First seen by Releasebot:
      Apr 27, 2026
    Gruntwork logo

    Terragrunt by Gruntwork

    v1.0.3

    Terragrunt releases v1.0.3 with new CAS and stack capabilities, including the --no-cas flag, stack integration for CAS, and broader discovery for catalog templates. It also adds mark-many-as-read and mark_glob_as_read, while fixing crashes, races, and hcl fmt diff output.

    โœจ New Features

    • --no-cas flag for disabling CAS per command

    The new --no-cas flag disables the CAS for a single invocation, even when the cas experiment is enabled. It is available on run, stack generate, and stack run.

    terragrunt stack generate --experiment cas --no-cas

    This is useful when you want to fall back to the standard getter path without unwinding experiment configuration.

    Generation and runs error when --no-cas is combined with update_source_with_cas = true on any reachable unit, stack, or terraform block, since relative sources in catalog repositories cannot be resolved without the CAS.

    ๐Ÿ› Bug Fixes

    • hcl fmt --diff no longer requires the diff binary

    Previously, terragrunt hcl fmt --diff spawned a diff process discovered in $PATH to render its output. This made it incompatible with minimal container images and Windows installations where that binary was unavailable.

    The flag now produces unified diff output without depending on any external binary.

    Note that the output is not byte-identical to GNU diff -u. Each file diff is now preceded by a diff old/&lt;path&gt; new/&lt;path&gt; header line, and the ---/+++ lines no longer include a trailing timestamp. Scripts that parsed the previous output may need small adjustments.

    • Fixed crash when include and locals with read_terragrunt_config coexist

    A unit that combined an include block with a locals block calling read_terragrunt_config(...) could crash during discovery, surfacing as a misleading Call to function "read_terragrunt_config" failed error pointed at the locals expression.

    Discovery now reports the underlying error instead of crashing.

    Reported in #5949.

    • Fixed crash when a root.hcl declares no remote_state block

    A root.hcl that only declared locals (or otherwise omitted a remote_state block) could trigger a nil pointer dereference inside Terragrunt's remote-state initialization. Downstream tools that embed the Terragrunt config parser, such as terragrunt-ls, crashed on every such file.

    A missing remote_state block now initializes an empty remote-state value instead of panicking, so parsing proceeds normally when no backend is configured.

    Reported in terragrunt-ls#134.

    Thanks to @SAY-5 for contributing this fix!

    • Fixed a race condition in terragrunt stack generate

    Fixed a race condition in terragrunt stack generate that could produce non-deterministic file errors on nested stack hierarchies. The same stack file could reach the worker pool twice through different path forms and cause the duplicate writes to conflict with each other. Paths are now normalized before dispatch so each stack file is generated exactly once per invocation.

    When a stack file is legitimately claimed by more than one parent during generation, Terragrunt now logs a warning that names the contending parents and records the latest claimant, so the configuration can be corrected before it causes silent overwrites.

    • Fixed data race in --version flag parsing under concurrent CLI invocations

    When multiple Terragrunt CLI invocations ran concurrently in the same process, urfave/cli/v2's package-level VersionFlag singleton was mutated concurrently by each invocation's flag-parsing path, producing a WARNING: DATA RACE on shared flag state.

    Terragrunt now sets cli.App.HideVersion = true at construction, which prevents urfave from auto-appending its shared VersionFlag into each App's flag set. The --version / -v flag is unchanged from the user's perspective โ€” it is handled by Terragrunt's own flag registered in internal/cli/flags/global.NewHelpVersionFlags.

    ๐Ÿงช Experiments Added

    • mark-many-as-read โ€” Mark many files as read in one step

    Enable the new mark-many-as-read experiment to turn on two behaviors that each mark many files as read in a single call: automatic marking of files inside a local terraform { source = "..." } block, and the new mark_glob_as_read HCL function.

    With the experiment on, a unit like this:

    # live/unit/terragrunt.hcl
    terraform {
      source = "../../modules/service"
    }
    

    records every *.tf, *.tf.json, *.hcl, *.tofu, and *.tofu.json file found under ../../modules/service (recursively) as read for the unit. Non-source files such as README.md are skipped. A reading-based filter expression such as --filter 'reading=../../modules/service/**' then matches every unit that points at the module, so a change to any file in the module cascades to its consumers.

    The same experiment also enables a new HCL function, mark_glob_as_read(pattern), which expands a glob using the same gobwas/glob syntax as filter expressions and marks every matching file as read. It returns the list of absolute paths that matched, so it composes with other expressions:

    locals {
      configs = mark_glob_as_read("${get_terragrunt_dir()}/config/{*.yaml,**/*.yaml}")
    }
    

    ** only collapses the surrounding separators when the adjacent segments are literals, so match-at-any-depth with a wildcard trailing segment is written as {*.yaml,**/*.yaml}. See the HCL reference for full pattern syntax.

    This is useful when a unit reads a collection of files indirectly (for example, via run_cmd or templatefile) and you want changes to any of them to trigger the unit through reading-based filters. Calling mark_glob_as_read without the experiment enabled returns an error.

    ๐Ÿงช Experiments Updated

    • cas โ€” Stack integration via update_source_with_cas

    The cas experiment now integrates with stacks. Units and terraform blocks can set update_source_with_cas = true to use relative source paths in catalog repositories, removing the need to plumb remote Git URLs through values expressions.

    # stacks/my-stack/terragrunt.stack.hcl (in your catalog repository)
    unit "service" {
      source = "../..//units/my-service"
      update_source_with_cas = true
      path = "service"
    }
    

    During stack generation, Terragrunt rewrites these relative sources to cas:: references that resolve against content stored in the CAS. The repository is cloned once, and subsequent stack generations resolve content from the local store without network access. Generated .terragrunt-stack files contain deterministic CAS references, so regeneration does not produce spurious diffs.

    CAS also supports SHA-256 repositories now, detected automatically via git rev-parse --show-object-format. The on-disk store layout was reorganized into blobs/ and trees/ namespaces under ~/.cache/terragrunt/cas/store/.

    To learn more, see the CAS documentation and Explicit Stacks: CAS Integration.

    • catalog-redesign โ€” Templates and .terragrunt-catalog-ignore

    The catalog-redesign experiment picked up user-visible improvements to discovery and filtering.

    Discovery walks the entire repository instead of only a modules/ directory, so modules and templates can live anywhere in the tree. Boilerplate templates (directories containing a .boilerplate/ subdirectory or a top-level boilerplate.yml) are discovered as a distinct component kind alongside OpenTofu/Terraform modules and labeled as templates in the UI. When a directory qualifies as both, it is classified as a template.

    Catalog authors can commit a .terragrunt-catalog-ignore file at the repo root to keep directories such as examples/ or test/ out of discovery. The file uses .gitignore-style semantics: one pattern per line, # for comments, ! for negation, and last match wins. Matching is anchored at the repo root; a lone * does not cross /, and ** does.

    # .terragrunt-catalog-ignore
    examples
    examples/**
    test/**
    !test/keep
    

    An --ignore-file flag (also available via TG_IGNORE_FILE) points at an additional ignore file that is layered on top of the repo's .terragrunt-catalog-ignore. The extra rules are appended under last-match-wins semantics, so the flag can either add new exclusions or re-include paths that the repo file excluded.

    To learn more, see Excluding paths from discovery.

    The list view is split into All, Modules, and Templates tabs, with All selected on launch so every discovered component is visible without switching views. Press tab and shift+tab to cycle between them; each tab keeps its own cursor and search filter.

    • stack-dependencies โ€” Multi-level nested stack.<name>.<nested_stack>.path references

    The stack-dependencies experiment already supported stack.<name>.path (a whole stack) and stack.<name>.<unit_name>.path (a unit inside a stack). It now also resolves references where the second segment is itself a nested stack, so an autoinclude block in a parent stack can target a stack that lives inside another stack:

    # live/terragrunt.stack.hcl
    stack "infra" {
      source = "../catalog/stacks/infra"
      path = "infra"
    }
    unit "app" {
      source = "../catalog/units/app"
      path = "app"
      autoinclude {
        dependency "deep" {
          config_path = stack.infra.deep.path
        }
      }
    }
    

    Here stack.infra.deep.path resolves to the generated directory of a stack "deep" block declared inside catalog/stacks/infra/terragrunt.stack.hcl. This makes deeper stack hierarchies addressable from a single dependency expression without flattening the layout.

    What's Changed

    • feat: Mark module sources as read by @yhakbar in #5963
    • feat: Adding support for CAS in stacks by @yhakbar in #5911
    • feat: Splitting modules from templates by @yhakbar in #5932
    • feat: allow units to depend on stacks by @denis256 in #5924
    • fix: Addressing panic in read_terragrunt_config() by @yhakbar in #5953
    • fix: version flag race condition fix by @denis256 in #5961
    • fix: fixing concurrent-write race in stack generate by @denis256 in #5962
    • fix(remotestate): survive nil Config passed to New by @SAY-5 in #5965
    • docs: Cleaning up changelog for v1.0.3 by @yhakbar in #5977
    • docs: Touching up docs for catalog-redesign by @yhakbar in #5940
    • docs: Adding Terragrunt Scale callout in README.md by @yhakbar in #5946
    • docs: Fixing Kapa integration by @yhakbar in #5942
    • chore: Integrating vexec into NewGitRunner by @yhakbar in #5934
    • chore: Offboarding Travis by @yhakbar in #5944
    • chore: Expanding lll coverage to amazonsts by @yhakbar in #5852
    • chore: Expanding lll coverage to runner-common by @yhakbar in #5853
    • chore: Expanding lll coverage to runner-graph by @yhakbar in #5854
    • chore: Expanding lll coverage to list by @yhakbar in #5855
    • chore: stacks generation improvements by @denis256 in #5969
    • chore: Expanding lll coverage to errorconfig by @yhakbar in #5856
    • chore: Expanding lll coverage to experiment by @yhakbar in #5857
    • chore: Expanding lll coverage to externalcmd by @yhakbar in #5858
    • chore: Expanding lll coverage to cache-controllers by @yhakbar in #5859
    • chore: Expanding lll coverage to vfs by @yhakbar in #5860
    • chore: Expanding lll coverage to stack by @yhakbar in #5841
    • chore: Expanding lll coverage to backend-delete by @yhakbar in #5842
    • chore: Expanding lll coverage to cloner by @yhakbar in #5843
    • chore: Expanding lll coverage to engine by @yhakbar in #5844
    • chore: Expanding lll coverage to git by @yhakbar in #5845
    • chore: Consolidating independent integration tests by @yhakbar in #5938
    • chore: Integrating signals into vexec by @yhakbar in #5935
    • chore: Expanding lll coverage to prepare by @yhakbar in #5846
    • chore: Expanding lll coverage to exec by @yhakbar in #5847
    • chore: Expanding lll coverage to find by @yhakbar in #5848
    • chore: Expanding lll coverage to stacks-output by @yhakbar in #5850
    • chore(deps): bump astro from 6.1.2 to 6.1.6 in /docs by @dependabot[bot] in #5947
    • chore: Expanding lll coverage to placeholders by @yhakbar in #5851
    • chore: Removing dependency on diff by @yhakbar in #5954
    • chore: Addressing feedback on #5953 and #5954 by @yhakbar in #5964
    • chore: Windows signing fix by @denis256 in #5979

    New Contributors

    • @SAY-5 made their first contribution in #5965

    Full Changelog: v1.0.2...v1.0.3

    Original source
Releasebot

Curated by the Releasebot team

Releasebot is an aggregator of official product update announcements from hundreds of software vendors and thousands of sources.

Our editorial process involves the manual review and audit of release notes procured with the help of automated systems.