Repository: gruntwork-io/terragrunt

Issue:
Terragrunt exclude block is supported only for `run-all`
## Describe the bug

The `skip` attribute is deprecated and the documentation says it should be replaced with an `exclude` block. However, the `exclude` block does not provide the same functionality. It seems like it is honoured only on `run-all` commands and not during regular `plan` or `apply` commands.

## Steps To Reproduce

The following works:

```hcl
skip = contains(local.environments, local.target) ? false : true

locals {
  environments = ["dev", "prod"]
  target       = "test"
}
```
This doesn't: 

```hcl
exclude {
  if                   = !contains(local.environments, local.target)
  actions              = ["all"]
  exclude_dependencies = true
}

locals {
  environments = ["dev", "prod"]
  target       = "test"
}
```

A regular plan is executed even though the requirement is not satisfied.

## Expected behavior

A clear and concise description of what you expected to happen.
```
terragrunt plan
17:34:20.994 INFO   Skipping terragrunt module ./terragrunt.hcl due to skip = true.
```
## Nice to haves

- [x] Terminal output
- [ ] Screenshots

## Versions

- Terragrunt version: 0.73.14
- OpenTofu/Terraform version: Terraform 1.10.3
- Environment details (Ubuntu 20.04, Windows 10, etc.): Ubuntu 24.04

## Additional context

Add any other context about the problem here.


Hints from issue comments:
@yhakbar: This is expected, and documented here:
https://terragrunt.gruntwork.io/docs/features/runtime-control/#exclusion-from-the-run-queue

Can you give an explanation for why you think this should be the default behavior?

The expected usage of the exclude block is to exclude some units from the run queue by default, so that incomplete units can be integrated into a shared codebase and to allow engineers to explicitly test the incomplete units when necessary.@velkovb: Because it is offered as a replacement of the `skip` attribute which work in a different way and can be used with `plan` and `apply` commands. As `skip` is deprecated, once it is removed there will be nothing to provide this functionality. Furthermore, it should be stated that is relevant in the context of `run-all` in the docs for the block itself so it is more visible for users.@yhakbar: Did you note the call out in the docs that a `before_hook` can be used to achieve the same benefit?

```hcl
terraform {
  before_hook "prevent_deploy" {
    commands = ["apply", "destroy"]
    execute  = local.ban_deploy ? ["bash", "-c", "echo 'Deploying on weekends is not allowed. Go home.' && exit 1"] : []
  }
}
```

That's a good idea! If you agree that users have access to the tooling that they need without changing the configuration block, please feel free to submit a pull request to update the documentation in a way that would have made the behavior obvious to you when you first looked at it.@velkovb: That is way uglier and actually throws an error which it shouldn't be. Will look into a PR. 

Would that mean that there are no plans to provide the existing `skip` attribute functionality?@yhakbar: I can understand a desire to keep things the same, but that behavior of exiting without error was also surprising to users.

When including a configuration with a `skip = true`, performing a run in a unit would just exit out with that easy to miss warning. It's especially unclear in CI systems and scripts.

If you have a use-case where you would prefer that the exclude block also exits early for a standalone run, I would like to learn more about it. Knowing that it has value for users in real world scenarios might convince us to change how the `exclude` block works, or provide some sort of attribute to adjust the behavior.@velkovb: I understand that the current behavior might be surprising in some cases, especially in CI systems where warnings can be easy to miss. However, from our perspective, skip = true is an intentional decision—it means the configuration is set up correctly and behaving as expected. Introducing an error in this case would create unnecessary failures, making deployments more confusing rather than clearer.

In our setup, we maintain a structured set of Terragrunt projects and deploy them across multiple environments for consistency. However, not all projects belong in every environment, so we use a configuration file to define where each project should be deployed. During execution, we check this file and skip the project if it isn't needed in the target environment. Seeing errors in this scenario would be misleading since the process is working exactly as designed.

Would you be open to discussing alternative ways to improve visibility in CI while preserving the expected behavior for cases like ours?@yhakbar: Yes! Let's discuss.

It seems like the solution that would work best for most users is to simply not add the unnecessary units in each environment, so as it currently stands, it doesn't seem like good _default_ behavior to have an early exit when performing a run on one unit.

Would a design that allowed users to opt in for an exclude to exit early instead of just excluding the unit from the run queue help?

Alternatively, maybe it would serve users better to have a configuration for the `before_hook` to exit early without error when a particular status code is detected?

I'd also like to understand better why it wouldn't be an error for a user to try to perform a run in a unit that shouldn't be used for a particular environment. Why is it better to exit with a status code of zero?@velkovb: In our use case we have a monorepository with hundreds of projects. Each project has a configuration file that has the following yaml:
```yaml
environments:
  - dev
  - prod
```

We use the same `terragrunt.hcl` with different input based on the environment to ensure consistence and to have promotion process. The current behaviour of the `skip` attributes allowed us to control the enabled environments for a project via this file and it cannot be accidentally applied in an environment that it is not enabled for. The other use case is when we plan multiple projects together but that will be using run-all so it should be fine with the exclude block.

```
└───project-a
        project.yaml
        terragrunt.hcl
        variables_dev.hcl
        variables_prod.hcl
```

That is why a project might be only in dev for example and it needs to be skipped in prod. 

On the suggestions, both seems like an option. We will be okay if it is not the default behavior but have the possibility to configure it. From my experience, the current `skip` attribute is behaving better than a hook as it is evaluated only once and may faster than hooks. If we go the hook route, from what I remember it is evaluated on each dependency and we could get several executions on the hooks. Not sure if I am mistaken but it also would be called twice if the hook is configured for `commands = ["init", "validate", "plan", "apply", "destroy" ], and we run plan - once for the implicit init and once for the plan@yhakbar: I see. That seems like a good use-case for early exit even when running directly on the unit.

e.g.

```
exclude {
    if = true
    early_exit = true # Also exit early when run directly on the unit.
}
```

In your use-case, though, wouldn't you prefer that an engineer get a non-zero exit code when they try to run the unit directly? That's bad behavior, right? If a user does `terragrunt apply -auto-approve`, they might think that they successfully applied the unit.@velkovb: Will have a discussion with the team around it. Maybe its just a bias because I am used to the current solution. What worries me more is that the hooks are executed multiple times, once per dependency. I remember having really bad time around destroys and hooks. That is why I was hyped about the exclude block attribute to exclude dependencies but ... :) @yhakbar: I can understand being frustrated when existing patterns change, especially if you feel like you're losing access to features.

We're trying to get to a stable configuration before 1.0 so we can make backwards compatibility guarantees going forward.@velkovb: If `skip` and `exlude` have different use cases, why are you deprecating the `skip` attribute?
@velkovb: > Did you note the call out in the docs that a `before_hook` can be used to achieve the same benefit?
> 
> terraform {
>   before_hook "prevent_deploy" {
>     commands = ["apply", "destroy"]
>     execute  = local.ban_deploy ? ["bash", "-c", "echo 'Deploying on weekends is not allowed. Go home.' && exit 1"] : []
>   }
> }
> That's a good idea! If you agree that users have access to the tooling that they need without changing the configuration block, please feel free to submit a pull request to update the documentation in a way that would have made the behavior obvious to you when you first looked at it.

Btw this seems incorrect. I get the following error message - ` Error with hook check_environment. Need at least one non-empty argument in 'execute'.` 

There should be something in the `[]`, i.e `["echo"]`

P.S The `before_hook` doesn't solve the problem of dependencies. If your dependency is not there you don't get error from the before hook but rather that the dependency doesn't exist.@yhakbar: What version of Terragrunt are you using?

```bash
$ which terragrunt
/Users/yousif/.local/share/mise/installs/terragrunt/0.76.3/terragrunt
```

```bash
$ bat terragrunt.hcl
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: terragrunt.hcl
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ locals {
   2   │   ban_deploy = true
   3   │ }
   4   │
   5   │ terraform {
   6   │   before_hook "prevent_deploy" {
   7   │     commands = ["apply", "destroy"]
   8   │     execute  = local.ban_deploy ? ["bash", "-c", "echo 'Deploying on weekends is not allowed. Go home.' && exit 1"] : []
   9   │   }
  10   │ }
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
```

```bash
$ bat main.tf
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: main.tf   <EMPTY>
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
```

```bash
$ terragrunt plan
10:52:37.155 INFO   tofu: Initializing the backend...
10:52:37.156 INFO   tofu: Initializing provider plugins...
10:52:37.156 INFO   tofu: OpenTofu has been successfully initialized!
10:52:37.156 INFO   tofu:
10:52:37.156 INFO   tofu: You may now begin working with OpenTofu. Try running "tofu plan" to see
10:52:37.156 INFO   tofu: any changes that are required for your infrastructure. All OpenTofu commands
10:52:37.156 INFO   tofu: should now work.
10:52:37.156 INFO   tofu: If you ever set or change modules or backend configuration for OpenTofu,
10:52:37.156 INFO   tofu: rerun this command to reinitialize your working directory. If you forget, other
10:52:37.156 INFO   tofu: commands will detect it and remind you to do so if necessary.
10:52:37.216 STDOUT tofu: No changes. Your infrastructure matches the configuration.
10:52:37.217 STDOUT tofu: OpenTofu has compared your real infrastructure against your configuration and
10:52:37.217 STDOUT tofu: found no differences, so no changes are needed.
```

```bash
$ terragrunt apply
10:52:41.241 INFO   tofu: Initializing the backend...
10:52:41.241 INFO   tofu: Initializing provider plugins...
10:52:41.242 INFO   tofu: OpenTofu has been successfully initialized!
10:52:41.242 INFO   tofu:
10:52:41.242 INFO   tofu: You may now begin working with OpenTofu. Try running "tofu plan" to see
10:52:41.242 INFO   tofu: any changes that are required for your infrastructure. All OpenTofu commands
10:52:41.242 INFO   tofu: should now work.
10:52:41.242 INFO   tofu: If you ever set or change modules or backend configuration for OpenTofu,
10:52:41.242 INFO   tofu: rerun this command to reinitialize your working directory. If you forget, other
10:52:41.243 INFO   tofu: commands will detect it and remind you to do so if necessary.
10:52:41.243 INFO   Executing hook: prevent_deploy
Deploying on weekends is not allowed. Go home.
10:52:41.247 ERROR  Error running hook prevent_deploy with message: Failed to execute "bash -c echo 'Deploying on weekends is not allowed. Go home.' && exit 1" in .

exit status 1
10:52:41.247 ERROR  Errors encountered running before_hooks. Not running 'terraform'.
10:52:41.247 ERROR  error occurred:

* Failed to execute "bash -c echo 'Deploying on weekends is not allowed. Go home.' && exit 1" in .

  exit status 1


```@michelzanini: I also use `skip` attribute. My use case is a little different.

I have a sandbox environment that we want to spin and destroy from time to time.
These sandbox modules are marked with `skip = true` to avoid running and creating then accidentally, including in CI.

If explicitly decided by the Ops person, he can bypass the `skip` flag using `--feature sandbox`.
Then he would use the environment, and destroy again after it tested.

I would like to avoid running it does not matter if its in a single apply or apply-all. The `skip` flag gives me that functionality.
Like it has been said already, if you plan to remove `skip`, then `exclude` should have an option to work the same as skip.

Something like your suggestion of including a flag in the `exclude` block would work well.@yhakbar: Given that multiple folks are asking for it, we can commit to providing some configuration on the `exclude` block to support the functionality of the `skip` attribute.

I'm not sure when we can deliver it, but we can commit to avoiding removal of the `skip` attribute until after the `exclude` block supports the ability to exit early when performing a direct run on the unit.

Two things I would like to ask for from both @velkovb and @michelzanini is to propose a preferred interface for this behavior, and preferred defaults for the exclude block. We can think through the design, and work out a solution that works for the use-cases we're envisioning for the block, including the use-cases you two have mentioned, and the desire to have continuous integration into a monorepo without automatically pulling the unit into `--all` runs.@michelzanini: I believe it makes sense for the `exclude` block to default to excluding only in run all commands by default (excluding from the run queue), like its today.

Using an additional flag to control single unit exclusion with a default `false` value seems the best and most simple option. The challenge would be to give the flag a good readable name.

Some suggestions `early_exit`, `skip_unit`, `skip_all`, `force_skip`, `force_early_exit`, etc....

@velkovb: That would be great, I am interested whether the `exclude_dependencies` flag will work in this scenario. 

I agree with @michelzanini around the defaults and the additional flag. For the naming I was thinking it probably should match the `exclude_dependencies`  one and be something like `exclude_on_direct_run` though it might be a bit too verbose.@yhakbar: Another option is `prevent_run`, meaning that the exclude block is also going to prevent actually running the unit, as opposed to merely excluding it from the run queue. Would that be intuitive? Would it make sense for the unit to always return an exit code of 0 in this case?

The `exclude_dependencies` attribute should only factor into the run queue. I think if you want dependencies to also be prevented from running when targeted directly, they should have their own exclude blocks.@velkovb: `prevent_run` sounds good as well. If we are to stick to current behaviour, exit code of 0 should be the case. I don't have strong preference on it.

On the dependencies, from my understanding by default Terragrunt will do a `terraform output` on the dependency and that doesn't evaluate even the skip attribute now. 

Solution patch:
diff --git a/cli/commands/run/run.go b/cli/commands/run/run.go
index aa54a92d..0a49c77d 100644
--- a/cli/commands/run/run.go
+++ b/cli/commands/run/run.go
@@ -308,7 +308,12 @@ func runTerragruntWithConfig(
 	r *report.Report,
 	target *Target,
 ) error {
-	// Add extra_arguments to the command
+	if cfg.Exclude != nil && cfg.Exclude.ShouldPreventRun(opts.TerraformCommand) {
+		l.Infof("Early exit in terragrunt unit %s due to exclude block with no_run = true", opts.WorkingDir)
+
+		return nil
+	}
+
 	if cfg.Terraform != nil && cfg.Terraform.ExtraArgs != nil && len(cfg.Terraform.ExtraArgs) > 0 {
 		args := FilterTerraformExtraArgs(l, opts, cfg)
 
diff --git a/config/config.go b/config/config.go
index eea8079b..9a429ddf 100644
--- a/config/config.go
+++ b/config/config.go
@@ -491,6 +491,10 @@ func (cfg *TerragruntConfig) WriteTo(w io.Writer) (int64, error) {
 			excludeBody.SetAttributeValue("actions", excludeAsCty.GetAttr("actions"))
 		}
 
+		if cfg.Exclude.NoRun != nil {
+			excludeBody.SetAttributeValue("no_run", excludeAsCty.GetAttr("no_run"))
+		}
+
 		excludeBody.SetAttributeValue("if", excludeAsCty.GetAttr("if"))
 
 		rootBody.AppendBlock(excludeBlock)
diff --git a/config/exclude.go b/config/exclude.go
index 4ec146e4..7f5f07ef 100644
--- a/config/exclude.go
+++ b/config/exclude.go
@@ -17,11 +17,12 @@ const (
 )
 
 // bool values to be used as booleans.
-var boolFlagValues = []string{"if", "exclude_dependencies"}
+var boolFlagValues = []string{"if", "exclude_dependencies", "no_run"}
 
 // ExcludeConfig configurations for hcl files.
 type ExcludeConfig struct {
 	ExcludeDependencies *bool    `cty:"exclude_dependencies" hcl:"exclude_dependencies,attr" json:"exclude_dependencies"`
+	NoRun               *bool    `cty:"no_run" hcl:"no_run,attr" json:"no_run"`
 	Actions             []string `cty:"actions" hcl:"actions,attr" json:"actions"`
 	If                  bool     `cty:"if" hcl:"if,attr" json:"if"`
 }
@@ -49,12 +50,18 @@ func (e *ExcludeConfig) IsActionListed(action string) bool {
 	return false
 }
 
+// ShouldPreventRun checks if the unit should be prevented from running based on the no_run attribute and current action.
+func (e *ExcludeConfig) ShouldPreventRun(action string) bool {
+	return e.NoRun != nil && *e.NoRun && e.If && e.IsActionListed(action)
+}
+
 // Clone returns a new instance of ExcludeConfig with the same values as the original.
 func (e *ExcludeConfig) Clone() *ExcludeConfig {
 	return &ExcludeConfig{
 		If:                  e.If,
 		Actions:             e.Actions,
 		ExcludeDependencies: e.ExcludeDependencies,
+		NoRun:               e.NoRun,
 	}
 }
 
@@ -67,6 +74,7 @@ func (e *ExcludeConfig) Merge(exclude *ExcludeConfig) {
 	}
 
 	e.ExcludeDependencies = exclude.ExcludeDependencies
+	e.NoRun = exclude.NoRun
 }
 
 // evaluateExcludeBlocks evaluates the exclude block in the parsed file.
diff --git a/docs-starlight/bun.lock b/docs-starlight/bun.lock
index 7dbabaf9..e7bf0cab 100644
--- a/docs-starlight/bun.lock
+++ b/docs-starlight/bun.lock
@@ -22,6 +22,9 @@
       },
     },
   },
+  "patchedDependencies": {
+    "@astrojs/starlight@0.35.1": "patches/@astrojs%2Fstarlight@0.35.1.patch",
+  },
   "overrides": {
     "estree-walker": "2.0.2",
   },
@@ -1216,6 +1219,12 @@
 
     "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
 
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
+
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
+
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
+
     "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
 
     "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -1276,6 +1285,14 @@
 
     "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
 
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
+
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
     "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
 
     "astro/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="],
@@ -1319,5 +1336,7 @@
     "astro/vite/rollup/@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.44.2", "", { "os": "win32", "cpu": "x64" }, "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA=="],
 
     "astro/vite/rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+    "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
   }
 }
diff --git a/docs-starlight/package.json b/docs-starlight/package.json
index 9edceb6d..62c79c12 100644
--- a/docs-starlight/package.json
+++ b/docs-starlight/package.json
@@ -23,13 +23,13 @@
     "starlight-links-validator": "^0.17.0",
     "tailwindcss": "^4.1.11"
   },
-  "patchedDependencies": {
-    "@astrojs/starlight@0.31.1": "patches/@astrojs%2Fstarlight@0.31.1.patch"
-  },
   "devDependencies": {
     "@types/bun": "^1.2.19"
   },
   "overrides": {
     "estree-walker": "2.0.2"
+  },
+  "patchedDependencies": {
+    "@astrojs/starlight@0.35.1": "patches/@astrojs%2Fstarlight@0.35.1.patch"
   }
 }
diff --git a/docs-starlight/patches/@astrojs%2Fstarlight@0.31.1.patch b/docs-starlight/patches/@astrojs%2Fstarlight@0.35.1.patch
similarity index 79%
rename from docs-starlight/patches/@astrojs%2Fstarlight@0.31.1.patch
rename to docs-starlight/patches/@astrojs%2Fstarlight@0.35.1.patch
index c5d6887c..ecbcadf5 100644
--- a/docs-starlight/patches/@astrojs%2Fstarlight@0.31.1.patch
+++ b/docs-starlight/patches/@astrojs%2Fstarlight@0.35.1.patch
@@ -1,28 +1,4 @@
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-2538170ae1618e76 b/.bun-tag-2538170ae1618e76
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-323fabb156a0a907 b/.bun-tag-323fabb156a0a907
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-3b56a907a33fee16 b/.bun-tag-3b56a907a33fee16
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-4b60784df559cef6 b/.bun-tag-4b60784df559cef6
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-517cdc68f1c60bd9 b/.bun-tag-517cdc68f1c60bd9
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-a1e18c2dc678aa2a b/.bun-tag-a1e18c2dc678aa2a
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-e77454ae7b650533 b/.bun-tag-e77454ae7b650533
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-f030ab8c37f3bf3e b/.bun-tag-f030ab8c37f3bf3e
-new file mode 100644
-index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
-diff --git a/node_modules/@astrojs/starlight/.bun-tag-fa07332e7343bccc b/.bun-tag-fa07332e7343bccc
+diff --git a/node_modules/@astrojs/starlight/.bun-tag-96642a5e7ef0db7a b/.bun-tag-96642a5e7ef0db7a
 new file mode 100644
 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
 diff --git a/user-components/file-tree-icons.ts b/user-components/file-tree-icons.ts
diff --git a/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx b/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
index bcaab971..e00229f2 100644
--- a/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
+++ b/docs-starlight/src/content/docs/04-reference/01-hcl/02-blocks.mdx
@@ -1488,9 +1488,10 @@ Syntax:
 # terragrunt.hcl
 
 exclude {
-    if = <boolean expression>           # Boolean expression to determine exclusion.
-    actions = ["<action>", ...]         # List of actions to exclude (e.g., "plan", "apply", "all", "all_except_output").
-    exclude_dependencies = <boolean>    # Boolean to determine if dependencies should also be excluded.
+    if                   = <boolean>         # Boolean to determine exclusion.
+    no_run               = <boolean>         # Boolean to prevent the unit from running (even when not using `--all`).
+    actions              = ["<action>", ...] # List of actions to exclude (e.g., "plan", "apply", "all", "all_except_output").
+    exclude_dependencies = <boolean>         # Boolean to determine if dependencies should also be excluded.
 }
 ```
 
@@ -1501,6 +1502,7 @@ Attributes:
 | `if`                   | boolean      | Condition to dynamically determine whether the unit should be excluded.                                                 |
 | `actions`              | list(string) | Specifies which actions to exclude when the condition is met. Options: `plan`, `apply`, `all`, `all_except_output` etc. |
 | `exclude_dependencies` | boolean      | Indicates whether the dependencies of the excluded unit should also be excluded (default: `false`).                     |
+| `no_run`               | boolean      | When `true` and `if` is `true`, prevents the unit from running entirely for both single unit commands and `run --all` commands, but only when the current action matches the `actions` list. |
 
 Examples:
 
@@ -1542,6 +1544,18 @@ exclude {
 
 This setup is useful for scenarios where output evaluation is still needed, even if other actions like `plan` or `apply` are excluded.
 
+```hcl
+# terragrunt.hcl
+
+exclude {
+    if      = true
+    no_run  = true
+    actions = ["plan"]
+}
+```
+
+This configuration prevents the unit from running when `if` is `true` AND the current action is "plan". The `no_run` attribute works for both single unit commands (like `terragrunt plan`) and `run --all` commands (like `terragrunt run --all plan`), but only when the current action matches the `actions` list.
+
 Consider using this for units that are expensive to continuously update, and can be opted in when necessary.
 
 ## errors
diff --git a/docs/_docs/04_reference/04-config-blocks-and-attributes.md b/docs/_docs/04_reference/04-config-blocks-and-attributes.md
index 3e66ae47..10a56884 100644
--- a/docs/_docs/04_reference/04-config-blocks-and-attributes.md
+++ b/docs/_docs/04_reference/04-config-blocks-and-attributes.md
@@ -1494,9 +1494,10 @@ Syntax:
 
 ```hcl
 exclude {
-    if = <boolean expression>           # Boolean expression to determine exclusion.
-    actions = ["<action>", ...]         # List of actions to exclude (e.g., "plan", "apply", "all", "all_except_output").
-    exclude_dependencies = <boolean>    # Boolean to determine if dependencies should also be excluded.
+    if                   = <boolean>          # Boolean to determine exclusion.
+    no_run               = <boolean>          # Boolean to prevent the unit from running (even when not using `--all`).
+    actions              = ["<action>", ...]  # List of actions to exclude (e.g., "plan", "apply", "all", "all_except_output").
+    exclude_dependencies = <boolean>          # Boolean to determine if dependencies should also be excluded.
 }
 ```
 
@@ -1507,6 +1508,7 @@ Attributes:
 | `if`                   | boolean      | Condition to dynamically determine whether the unit should be excluded.                                                 |
 | `actions`              | list(string) | Specifies which actions to exclude when the condition is met. Options: `plan`, `apply`, `all`, `all_except_output` etc. |
 | `exclude_dependencies` | boolean      | Indicates whether the dependencies of the excluded unit should also be excluded (default: `false`).                     |
+| `no_run`               | boolean      | When `true` and `if` is `true`, prevents the unit from running entirely for both single unit commands and `run --all` commands, but only when the current action matches the `actions` list. |
 
 Examples:
 
@@ -1542,6 +1544,16 @@ exclude {
 
 This setup is useful for scenarios where output evaluation is still needed, even if other actions like `plan` or `apply` are excluded.
 
+```hcl
+exclude {
+    if      = true
+    no_run  = true
+    actions = ["plan"]
+}
+```
+
+This configuration prevents the unit from running when `if` is `true` AND the current action is "plan". The `no_run` attribute works for both single unit commands (like `terragrunt plan`) and `run --all` commands (like `terragrunt run --all plan`), but only when the current action matches the `actions` list.
+
 Consider using this for units that are expensive to continuously update, and can be opted in when necessary.
 
 ### errors



Test patch:
diff --git a/test/fixtures/exclude/no-run/conditional-no-run-unit/main.tf b/test/fixtures/exclude/no-run/conditional-no-run-unit/main.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/fixtures/exclude/no-run/conditional-no-run-unit/main.tf
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/exclude/no-run/conditional-no-run-unit/terragrunt.hcl b/test/fixtures/exclude/no-run/conditional-no-run-unit/terragrunt.hcl
new file mode 100644
index 00000000..89b2a586
--- /dev/null
+++ b/test/fixtures/exclude/no-run/conditional-no-run-unit/terragrunt.hcl
@@ -0,0 +1,13 @@
+terraform {
+  source = "."
+}
+
+feature "enable_unit" {
+  default = false
+}
+
+exclude {
+  if      = !feature.enable_unit.value
+  no_run  = true
+  actions = ["all"]
+}
diff --git a/test/fixtures/exclude/no-run/no-run-independent/main.tf b/test/fixtures/exclude/no-run/no-run-independent/main.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/fixtures/exclude/no-run/no-run-independent/main.tf
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/exclude/no-run/no-run-independent/terragrunt.hcl b/test/fixtures/exclude/no-run/no-run-independent/terragrunt.hcl
new file mode 100644
index 00000000..9107414d
--- /dev/null
+++ b/test/fixtures/exclude/no-run/no-run-independent/terragrunt.hcl
@@ -0,0 +1,9 @@
+terraform {
+  source = "."
+}
+
+exclude {
+  if      = true
+  no_run  = true
+  actions = ["plan"]
+}
diff --git a/test/fixtures/exclude/no-run/no-run-unit/main.tf b/test/fixtures/exclude/no-run/no-run-unit/main.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/fixtures/exclude/no-run/no-run-unit/main.tf
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/exclude/no-run/no-run-unit/terragrunt.hcl b/test/fixtures/exclude/no-run/no-run-unit/terragrunt.hcl
new file mode 100644
index 00000000..30c80f43
--- /dev/null
+++ b/test/fixtures/exclude/no-run/no-run-unit/terragrunt.hcl
@@ -0,0 +1,9 @@
+terraform {
+  source = "."
+}
+
+exclude {
+  if      = true
+  no_run  = true
+  actions = ["all"]
+}
diff --git a/test/fixtures/exclude/no-run/normal-unit/main.tf b/test/fixtures/exclude/no-run/normal-unit/main.tf
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/fixtures/exclude/no-run/normal-unit/main.tf
@@ -0,0 +1 @@
+
diff --git a/test/fixtures/exclude/no-run/normal-unit/terragrunt.hcl b/test/fixtures/exclude/no-run/normal-unit/terragrunt.hcl
new file mode 100644
index 00000000..c99c2995
--- /dev/null
+++ b/test/fixtures/exclude/no-run/normal-unit/terragrunt.hcl
@@ -0,0 +1,3 @@
+terraform {
+  source = "."
+}
diff --git a/test/integration_exclude_test.go b/test/integration_exclude_test.go
index d010fa86..8f797527 100644
--- a/test/integration_exclude_test.go
+++ b/test/integration_exclude_test.go
@@ -16,6 +16,7 @@ const (
 	testExcludeByFlags         = "fixtures/exclude/feature-flags"
 	testExcludeDependencies    = "fixtures/exclude/dependencies"
 	testExcludeAllExceptOutput = "fixtures/exclude/all-except-output"
+	testExcludeNoRun           = "fixtures/exclude/no-run"
 )
 
 func TestExcludeByDefault(t *testing.T) {
@@ -156,3 +157,70 @@ func TestExcludeAllExceptOutput(t *testing.T) {
 	assert.Contains(t, stderr, "app1")
 	assert.Contains(t, stderr, "app2")
 }
+
+func TestExcludeNoRunSingleUnit(t *testing.T) {
+	t.Parallel()
+
+	cleanupTerraformFolder(t, testExcludeNoRun)
+	tmpEnvPath := helpers.CopyEnvironment(t, testExcludeNoRun)
+	rootPath := util.JoinPath(tmpEnvPath, testExcludeNoRun, "no-run-unit")
+
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt plan --non-interactive --working-dir "+rootPath)
+
+	require.NoError(t, err)
+	assert.Contains(t, stderr, "Early exit in terragrunt unit")
+	assert.Contains(t, stderr, "due to exclude block with no_run = true")
+}
+
+func TestExcludeNoRunRunAll(t *testing.T) {
+	t.Parallel()
+
+	cleanupTerraformFolder(t, testExcludeNoRun)
+	tmpEnvPath := helpers.CopyEnvironment(t, testExcludeNoRun)
+	rootPath := util.JoinPath(tmpEnvPath, testExcludeNoRun)
+
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --all plan --non-interactive --working-dir "+rootPath)
+
+	require.NoError(t, err)
+	assert.Contains(t, stderr, "normal-unit")
+	assert.NotContains(t, stderr, "no-run-unit")
+}
+
+func TestExcludeNoRunConditional(t *testing.T) {
+	t.Parallel()
+
+	cleanupTerraformFolder(t, testExcludeNoRun)
+	tmpEnvPath := helpers.CopyEnvironment(t, testExcludeNoRun)
+	rootPath := util.JoinPath(tmpEnvPath, testExcludeNoRun, "conditional-no-run-unit")
+
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt plan --non-interactive --working-dir "+rootPath)
+
+	require.NoError(t, err)
+	assert.Contains(t, stderr, "Early exit in terragrunt unit")
+	assert.Contains(t, stderr, "due to exclude block with no_run = true")
+
+	_, stderr, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt plan --feature enable_unit=true --non-interactive --working-dir "+rootPath)
+
+	require.NoError(t, err)
+	assert.NotContains(t, stderr, "Early exit in terragrunt unit")
+}
+
+func TestExcludeNoRunIndependentOfActions(t *testing.T) {
+	t.Parallel()
+
+	cleanupTerraformFolder(t, testExcludeNoRun)
+	tmpEnvPath := helpers.CopyEnvironment(t, testExcludeNoRun)
+	rootPath := util.JoinPath(tmpEnvPath, testExcludeNoRun, "no-run-independent")
+
+	_, stderr, err := helpers.RunTerragruntCommandWithOutput(t, "terragrunt run --non-interactive --working-dir "+rootPath+" -- apply -auto-approve")
+
+	require.NoError(t, err)
+	assert.NotContains(t, stderr, "Early exit in terragrunt unit")
+	assert.NotContains(t, stderr, "due to exclude block with no_run = true")
+
+	_, stderr, err = helpers.RunTerragruntCommandWithOutput(t, "terragrunt plan --non-interactive --working-dir "+rootPath)
+
+	require.NoError(t, err)
+	assert.Contains(t, stderr, "Early exit in terragrunt unit")
+	assert.Contains(t, stderr, "due to exclude block with no_run = true")
+}