# atmos describe affected

Use this command to show a list of the affected Atmos components and stacks given two Git commits.

_\[Video: atmos describe affected]_

## Description

The command uses two different Git commits to produce a list of affected Atmos components and stacks.

For the first commit, the command assumes that the current repo root is a Git checkout. An error will be thrown if the
current repo is not a Git repository (the `.git/` folder does not exist or is configured incorrectly).

The second commit can be specified on the command line by using the `--base` flag, which accepts both
[Git References](https://git-scm.com/book/en/v2/Git-Internals-Git-References) and commit SHAs (auto-detected by format).
The deprecated `--ref` and `--sha` flags are still supported for backward compatibility.

:::tip Zero-Config CI
When `ci.enabled` is `true` in `atmos.yaml`, the base commit is automatically resolved from the CI environment:

- **Pull requests**: Compares against the **fork point** of the PR branch (`git merge-base HEAD origin/<target>`). This works correctly even when the PR is out of date with the target branch — only commits the PR introduced are reported as affected, never commits that landed on `<target>` after the PR was forked. In shallow CI checkouts (`actions/checkout@v6` defaults to `fetch-depth: 1`), Atmos transparently fetches the target branch on demand.
- **Push events**: Compares against the previous commit
- **Merge groups**: Compares against the merge queue base

No `--base`, `--ref`, or `--sha` flags needed — just run `atmos describe affected` in your CI workflow.

If no CI environment is detected (or `ci.enabled` is not set), the default is `refs/remotes/origin/HEAD` (usually the `main` branch).
:::

## How does it work?

The command performs the following:

- If the `--repo-path` flag is passed, the command uses it as the path to the already cloned target repo with which to
  compare the current working branch. I this case, the command will not clone and checkout the
  target reference, but instead will use the already cloned one to compare the current branch with. In this case, the
  `--ref`, `--sha`, `--ssh-key` and `--ssh-key-password` flags are not used, and an error will be thrown if the `--repo-path`
  flag and any of the `--ref`, `--sha`, `--ssh-key` or `--ssh-key-password` flags are provided at the same time

- Otherwise, if the `--clone-target-ref=true` flag is specified, the command clones (into a temp directory) the remote
  target with which to compare the current working branch. If the `--ref` flag or the commit SHA flag `--sha` are provided,
  the command uses them to clone and checkout the remote target. Otherwise, the `HEAD` of the remote origin is
  used (`refs/remotes/origin/HEAD` Git ref, usually the `main` branch)

- Otherwise, (if the `--repo-path` and `--clone-target-ref=true` flags are not passed), the command does not clone anything
  from the remote origin, but instead just copies the current repo into a temp directory and checks out the target
  reference with which to compare the current working branch.
  If the `--ref` flag or the commit SHA flag `--sha` are
  provided, the command uses them to check out. Otherwise, the `HEAD` of the remote origin is used
  (`refs/remotes/origin/HEAD` Git ref, usually the `main` branch).
  This requires that the target reference is already cloned by Git, and the information about it exists in
  the `.git` directory (in case of using a non-default branch as the target, Git deep clone needs to be executed instead
  of a shallow clone).
  This is the recommended way to execute the `atmos describe affected` command since it allows
  [working with private repositories](#working-with-private-repositories) without providing the SSH credentials
  (`--ssh-key` and `--ssh-key-password` flags), since in this case Atmos does not access the remote origin and instead
  just checks out the target reference (which is already on the local file system)

- The command deep-merges all stack configurations from both sources: the current working branch and the target reference

- The command searches for changes in the component directories

- The command compares each stack manifest section of the stack configurations from both sources looking for differences

- And finally, the command outputs a JSON or YAML document consisting of a list of the affected components and stacks
  and what caused it to be affected

Since Atmos first checks the component folders for changes, if it finds any affected files, it will mark all related
components and stacks as affected. Atmos will then skip evaluating the stacks for differences since it already
knows that they are affected.
:::tip Use in GitHub Actions
Run `atmos describe affected --format=matrix` directly in your workflow to fan out across affected components. When `ci.enabled` is `true` in `atmos.yaml`, the matrix is automatically written to `$GITHUB_OUTPUT`. See the [Deploy Affected workflow](/ci#deploy-affected) for the full pattern.
:::

## CI Auto-Detection

When `ci.enabled` is `true` in `atmos.yaml` and no `--base` flag is provided, `describe affected` automatically
resolves the base commit from the CI environment. This is **provider-agnostic** — each CI provider implements its own
resolution logic.

### GitHub Actions

| Event | Base Resolution |
|-------|----------------|
| `pull_request` (opened/synchronize) | `git merge-base(HEAD, origin/<target>)` — fork point. Falls back to `event.pull_request.base.sha` if merge-base is unavailable. |
| `pull_request` (closed/merged) | `git merge-base`, then `HEAD~1` for merge-commit checkouts |
| `push` | Previous HEAD from event payload (`before`) |
| `push` (force-push) | Parent of current commit (`HEAD~1`) |
| `merge_group` | Target branch (`GITHUB_BASE_REF`) |

The `pull_request` strategy uses `git merge-base` as the gold standard. When the local repository is a shallow checkout (the default for `actions/checkout@v6`), Atmos automatically runs a targeted `git fetch origin <target>` and retries — you do not need to set `fetch-depth: 0` on your checkout step. If even the auto-fetch fails (offline runner, deeply orphaned history), Atmos falls back to `event.pull_request.base.sha` from the event payload, which is at worst stale by however many commits have landed on `<target>` since the PR was last synced.

**Example workflow** — no `--ref` or `--sha` needed:

```yaml
- name: Describe affected
  run: atmos describe affected --format=matrix
```

When `ci.enabled` is `true` in `atmos.yaml` and `GITHUB_OUTPUT` is available (e.g. on GitHub Actions), the matrix output is written there automatically. Outside of GitHub Actions, or when `GITHUB_OUTPUT` is unset, output falls back to stdout.

### Flag Precedence

1. `--base` flag (explicit)
2. `--ref` / `--sha` flags (deprecated, backward compatible)
3. CI auto-detection (when `ci.enabled` is `true`)
4. `refs/remotes/origin/HEAD` (default)

## Usage

```shell
atmos describe affected [options]
```

:::info YAML Functions and Authentication
By default, `atmos describe affected` executes YAML template functions (e.g., `!terraform.state`, `!terraform.output`) and Go templates during stack processing. When these functions access remote resources requiring authentication, use the `--identity` flag to authenticate before execution. You can disable function/template processing with `--process-functions=false` or `--process-templates=false` flags.
:::

:::tip
Run `atmos describe affected --help` to see all the available options
:::

## Examples

```shell
# Zero-config in CI (auto-detects base when ci.enabled is true)
atmos describe affected
# Explicit base commit (ref or SHA)
atmos describe affected --base main
atmos describe affected --base refs/tags/v1.16.0
atmos describe affected --base 3a5eafeab90426bd82bf5899896b28cc0bab3073
atmos describe affected --base refs/heads/main --format json
atmos describe affected --base refs/tags/v1.16.0 --file affected.yaml --format yaml
atmos describe affected --verbose=true
atmos describe affected --ssh-key <path_to_ssh_key>
atmos describe affected --ssh-key <path_to_ssh_key> --ssh-key-password <password>
atmos describe affected --repo-path <path_to_already_cloned_repo>
atmos describe affected --include-spacelift-admin-stacks=true
atmos describe affected --clone-target-ref=true
atmos describe affected --include-dependents=true
atmos describe affected --include-settings=true
atmos describe affected --stack=plat-ue2-prod
atmos describe affected --upload=true
atmos describe affected --query <yq-expression>
atmos describe affected --process-templates=false
atmos describe affected --process-functions=false
atmos describe affected --skip=terraform.output
atmos describe affected --skip=terraform.output --skip=include
atmos describe affected --skip=include,eval
atmos describe affected --exclude-locked
# Authenticate before describing (when YAML functions require credentials)
atmos describe affected --identity my-aws-identity
atmos describe affected --identity # Interactive selection
# Disable authentication (use AWS SDK defaults)
atmos describe affected --identity=false
atmos describe affected -i my-aws-identity --ref refs/heads/main
# Filter to show only deleted components (for destruction workflows)
atmos describe affected --query '[.[] | select(.deleted == true)]'
# Filter to show only modified components (for apply workflows)
atmos describe affected --query '[.[] | select(.deleted != true)]'
```

## Example Output

```shell
> atmos describe affected --verbose=true

Cloning repo 'https://github.com/cloudposse/atmos' into the temp dir '/var/folders/g5/lbvzy_ld2hx4mgrgyp19bvb00000gn/T/16710736261366892599'

Checking out the HEAD of the default branch ...

Enumerating objects: 4215, done.
Counting objects: 100% (1157/1157), done.
Compressing objects: 100% (576/576), done.
Total 4215 (delta 658), reused 911 (delta 511), pack-reused 3058

Checked out Git ref 'refs/heads/main'

Current HEAD: 7d37c1e890514479fae404d13841a2754be70cbf refs/heads/describe-affected
BASE: 40210e8d365d3d88ac13c0778c0867b679bbba69 refs/heads/main

Changed files:

tests/fixtures/scenarios/complete/components/terraform/infra/vpc/main.tf
internal/exec/describe_affected.go
website/docs/cli/commands/describe/describe-affected.md

Affected components and stacks:

[
   {
      "component": "infra/vpc",
      "component_type": "terraform",
      "component_path": "components/terraform/infra/vpc",
      "stack": "tenant1-ue2-dev",
      "stack_slug": "tenant1-ue2-dev-infra-vpc",
      "spacelift_stack": "tenant1-ue2-dev-infra-vpc",
      "atlantis_project": "tenant1-ue2-dev-infra-vpc",
      "affected": "component"
   },
   {
      "component": "infra/vpc",
      "component_type": "terraform",
      "component_path": "components/terraform/infra/vpc",
      "stack": "tenant1-ue2-prod",
      "stack_slug": "tenant1-ue2-prod-infra-vpc",
      "spacelift_stack": "tenant1-ue2-prod-infra-vpc",
      "atlantis_project": "tenant1-ue2-prod-infra-vpc",
      "affected": "component"
   },
   {
      "component": "infra/vpc",
      "component_type": "terraform",
      "component_path": "components/terraform/infra/vpc",
      "stack": "tenant1-ue2-staging",
      "stack_slug": "tenant1-ue2-staging-infra-vpc",
      "spacelift_stack": "tenant1-ue2-staging-infra-vpc",
      "atlantis_project": "tenant1-ue2-staging-infra-vpc",
      "affected": "component"
   },
     {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "file",
    "file": "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
  },
  {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "folder",
    "folder": "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
  }
]
```

## Flags

- **`--base` (optional)**

  The base commit (ref or SHA) to compare against. Accepts both
  [Git References](https://git-scm.com/book/en/v2/Git-Internals-Git-References) and commit SHAs —
  the format is auto-detected.

  When `ci.enabled` is `true` in `atmos.yaml` and no base is provided, the base is automatically
  resolved from the CI environment (e.g., `GITHUB_BASE_REF` for pull requests).

  `atmos describe affected --base main`

  `atmos describe affected --base 3a5eafeab90426bd82bf5899896b28cc0bab3073`
- **`--ref` (deprecated)**

  **Deprecated: use `--base` instead.**
  [Git Reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References) with which to compare the current working branch.
- **`--sha` (deprecated)**

  **Deprecated: use `--base` instead.**
  Git commit SHA with which to compare the current working branch.
- **`--file` (optional)**

  If specified, write the result to the file
- **`--format` (optional)**

  Specify the output format: `json` or `yaml` (`json` is default)
- **`--ssh-key` (optional)**

  Path to PEM-encoded private key to clone private repos using SSH
- **`--ssh-key-password` (optional)**

  Encryption password for the PEM-encoded private key if the key contains a password-encrypted PEM block
- **`--repo-path` (optional)**

  Path to the already cloned target repository with which to compare the current branch. Conflicts with `--base`, `--ref`, `--sha`, `--ssh-key` and `--ssh-key-password`
- **`--verbose` (optional)**

  Print more detailed output when cloning and checking out the target Git repository and processing the result
- **`--include-spacelift-admin-stacks` (optional)**

  Include the Spacelift admin stack of any stack that is affected by config changes
- **`--clone-target-ref` (optional)**

  Clone the target reference with which to compare the current branch.

  `atmos describe affected --clone-target-ref=true`

  If set to `false` (default), the target reference will be checked out instead.
  This requires that the target reference is already cloned by Git, and the information about it exists in the `.git` directory
- **`--stack` (optional)**

  Only show results for the specific stack.

  `atmos describe affected --stack=plat-ue2-prod`
- **`--include-dependents` (optional)**

  Include the dependent components and stacks.

  `atmos describe affected --include-dependents=true`
- **`--include-settings` (optional)**

  Include the `settings` section for each affected component.

  `atmos describe affected --include-settings=true`
- **`--query` (optional)**

  Query the results of the command using YQ expressions.

  `atmos describe affected --query=<yq-expression>`

  For more details, refer to [YQ - a lightweight and portable command-line YAML processor](https://mikefarah.gitbook.io/yq)
- **`--process-templates` (optional)**

  Enable/disable processing of `Go` templates in Atmos stacks manifests when executing the command.
  If the flag is not provided, it's set to `true` by default.

  `atmos describe affected --process-templates=false`
- **`--process-functions` (optional)**

  Enable/disable processing of Atmos YAML functions in Atmos stacks manifests when executing the command.
  If the flag is not provided, it's set to `true` by default.

  `atmos describe affected --process-functions=false`
- **`--skip` (optional)**

  Skip processing a specific Atmos YAML function in Atmos stacks manifests when executing the command.
  To specify more than one function, use multiple `--skip` flags, or separate the functions with a comma:

  `atmos describe affected --skip=terraform.output --skip=include`

  `atmos describe affected --skip=terraform.output,include`
- **`--exclude-locked` (optional)**

  Exclude the locked components (`metadata.locked: true`) from the output.

  Refer to [Locking Components with `metadata.locked`](/stacks/components#locking-components-metadatalocked)

  `atmos describe affected --exclude-locked`
- **`--upload` (optional)**

  Upload the affected components and stacks to a specified HTTP endpoint.

  `atmos describe affected --upload=true`

  Atmos will perform an HTTP POST request to the URL `${ATMOS_PRO_BASE_URL}/${ATMOS_PRO_ENDPOINT}`,
  where the base URL is defined by the `ATMOS_PRO_BASE_URL` environment variable,
  and the URL path is defined by the `ATMOS_PRO_ENDPOINT`environment variable
- **`--identity` / `-i` (optional)**

  Authenticate with a specific identity before describing affected components.
  This is required when YAML template functions (e.g., `!terraform.state`, `!terraform.output`)
  need to access remote resources requiring authentication.
  Use without a value for interactive identity selection:

  `atmos describe affected --identity` (interactive)

  `atmos describe affected --identity my-aws-identity` (specific identity)

  For more details, refer to [Authentication](/cli/commands/auth/usage).

## Output

The command outputs a list of objects (in JSON or YAML format).

Each object has the following schema:

```json
{
  "component": "....",
  "component_type": "....",
  "component_path": "....",
  "stack": "....",
  "stack_slug": "....",
  "spacelift_stack": ".....",
  "atlantis_project": ".....",
  "affected": ".....",
  "affected_all": [],
  "file": ".....",
  "folder": ".....",
  "dependents": [],
  "included_in_dependents": "true | false",
  "settings": {},
  "deleted": "true | false",
  "deletion_type": "component | stack"
}
```

where:

- **`component`**

  The affected Atmos component.
- **`component_type`**

  The type of the component (`terraform` or `helmfile`).
- **`component_path`**

  The filesystem path to the `terraform` or `helmfile` component.
- **`stack`**

  The affected Atmos stack.
- **`stack_slug`**

  The Atmos stack slug (concatenation of the Atmos stack and Atmos component).
- **`spacelift_stack`**

  The affected Spacelift stack. It will be included only if the Spacelift workspace is enabled for the Atmos component in the
  Atmos stack in the `settings.spacelift.workspace_enabled` section (either directly in the component's `settings.spacelift.workspace_enabled` section
  or via inheritance).
- **`atlantis_project`**

  The affected Atlantis project name. It will be included only if the Atlantis integration is configured in
  the `settings.atlantis` section in the stack config. Refer to [Atlantis Integration](/cli/configuration/integrations/atlantis) for more details.
- **`file`**

  If the Atmos component depends on an external file, and the file was changed,
  the `file` attributes shows the modified file.
- **`folder`**

  If the Atmos component depends on an external folder, and any file in the folder was changed,
  the `folder` attributes shows the modified folder.
- **`dependents`**

  A list of components that depend on the current affected component. It will be populated only if the
  command-line flag `--include-dependents=true` is passed (to take dependencies into account) and there are other components
  that depend on the affected component in the stack.
  Refer to [`atmos describe dependents`](/cli/commands/describe/dependents) for more details. The `dependents` property is
  hierarchical - each component in the list will also contain a `dependents` property if that component has dependent
  components as well.
- **`settings`**

  The `settings` section of the component in the stack. It will be included only if the
  command-line flag `--include-settings=true` is passed. The `settings` sections is a free-form map used to pass
  configuration information to [integrations](/cli/configuration/integrations).
- **`deleted`**

  A boolean flag set to `true` when the component exists in the BASE branch but has been deleted in HEAD.
  This enables CI/CD pipelines to identify components that require `terraform destroy`.
- **`deletion_type`**

  The type of deletion when `deleted` is `true`. Possible values:
  - `component`: The component was removed from the stack (the stack still exists).
  - `stack`: The entire stack was deleted (all components in it are marked as deleted).
- **`included_in_dependents`**

  A boolean flag indicating if the affected component in the stack is also present in any of the `dependents`
  properties of the other affected components. It will be included only if the command-line flag `--include-dependents=true`
  is passed. If `included_in_dependents` is set to `true`, it indicates that the affected component in the stack is also
  present in any of the `dependents` lists in the dependency hierarchy of the other affected components.
  This flag can be used to decide whether to plan/apply the affected component - you might skip planning/applying the component
  since it's also a dependency of another affected component and will be triggered in the dependency order of the other
  affected component.
- **`affected`**

  Shows the first (in the processing order) section that was changed. The possible values are:
  - **`stack.vars`**

    The `vars` component section in the stack config has been modified.
  - **`stack.env`**

    The `env` component section in the stack config has been modified.
  - **`stack.settings`**

    The `settings` component section in the stack config has been modified.
  - **`stack.metadata`**

    The `metadata` component section in the stack config has been modified.
  - **`component`**

    The Terraform or Helmfile component that the Atmos component provisions has been changed.
  - **`component.module`**

    The Terraform component is affected because it uses a local Terraform module (not from the Terraform registry, but from the
    local filesystem), and that local module has been changed.

    For example, let's suppose that we have a catalog of reusable Terraform modules in the `modules` folder (outside the `components` folder), and
    we have defined the following `label` Terraform module in `modules/label`:
    ```hcl title="modules/label"
      module "label" {
        source  = "cloudposse/label/null"
        version = "0.25.0"
        context = module.this.context
      }

      output "label" {
        value       = module.label
        description = "Label outputs"
      }
    ```
    We then use the Terraform module in the `components/terraform/top-level-component1` component:
    ```hcl title="components/terraform/top-level-component1"
      module "service_2_label" {
        source  = "../../../modules/label"
        context = module.this.context
      }

      output "service_2_id" {
        value       = module.service_2_label.label.id
        description = "Service 2 ID"
      }
    ```
    The `label` module is not in the stack config of the `top-level-component1` component (not in the YAML stack config files), but Atmos
    understands Terraform dependencies (using a Terraform parser from HashiCorp), and can automatically detect any changes to the module.

    For example, if you make changes to any files in the folder `modules/label`, Atmos will detect the module changes, and since the module is a
    Terraform dependency of the `top-level-component1` component, Atmos will mark the component as affected with the `affected` attribute
    set to `component.module`:
    ```json
      [
        {
          "component": "top-level-component1",
          "component_type": "terraform",
          "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
          "stack": "tenant1-ue2-staging",
          "stack_slug": "tenant1-ue2-staging-top-level-component1",
          "spacelift_stack": "tenant1-ue2-staging-top-level-component1",
          "atlantis_project": "tenant1-ue2-staging-top-level-component1",
          "affected": "component.module",
          "affected_all": [
            "component.module"
          ]
        },
        {
          "component": "top-level-component1",
          "component_type": "terraform",
          "component_path": "tests/fixtures/scenarios/complete/components/terraform/top-level-component1",
          "stack": "tenant2-ue2-staging",
          "stack_slug": "tenant2-ue2-staging-top-level-component1",
          "spacelift_stack": "tenant2-ue2-staging-top-level-component1",
          "atlantis_project": "tenant2-ue2-staging-top-level-component1",
          "affected": "component.module",
          "affected_all": [
            "component.module"
          ]
        }
      ]
    ```
  - **`stack.settings.spacelift.admin_stack_selector`**

    The Atmos component for the Spacelift admin stack.

    This will be included only if all of the following is true:
    - The `atmos describe affected` is executed with the `--include-spacelift-admin-stacks=true` flag

    - Any of the affected Atmos components has configured the section `settings.spacelift.admin_stack_selector` pointing to the Spacelift admin
      stack that manages the components.

      For example:

      ```yaml title="stacks/orgs/cp/tenant1/_defaults.yaml"
      settings:
        spacelift:
          # All Spacelift child stacks for the `tenant1` tenant are managed by the
          # `tenant1-ue2-prod-infrastructure-tenant1` Spacelift admin stack.
          # The `admin_stack_selector` attribute is used to find the affected Spacelift
          # admin stack for each affected Atmos stack
          # when executing the command
          # `atmos describe affected --include-spacelift-admin-stacks=true`
          admin_stack_selector:
            component: infrastructure-tenant1
            tenant: tenant1
            environment: ue2
            stage: prod
      ```

    - The Spacelift admin stack is enabled by `settings.spacelift.workspace_enabled` set to `true`.

      For example:

      ```yaml title="stacks/catalog/terraform/spacelift/infrastructure-tenant1.yaml"
      components:
        terraform:
          infrastructure-tenant1:
            metadata:
              component: spacelift
              inherits:
                - spacelift-defaults
            settings:
              spacelift:
                workspace_enabled: true
      ```
  - **`file`**

    An external file on the local filesystem that the Atmos component depends on was changed.

    Dependencies on external files (not in the component's folder) are defined using the `file` attribute in the `settings.depends_on` map.

    For example:
    ```yaml title="stacks/catalog/terraform/top-level-component3.yaml"
    components:
      terraform:
        top-level-component3:
          metadata:
            component: "top-level-component1"
          settings:
            depends_on:
              1:
                file: "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
    ```
    In the configuration above, we specify that the Atmos component `top-level-component3` depends on the file
    `tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf` (which is not in the component's folder). If the file gets modified,
    the component `top-level-component3` will be included in the `atmos describe affected` command output.

    For example:
    ```json
      [
        {
          "component": "top-level-component3",
          "component_type": "terraform",
          "component_path": "components/terraform/top-level-component1",
          "stack": "tenant1-ue2-test-1",
          "stack_slug": "tenant1-ue2-test-1-top-level-component3",
          "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
          "affected": "file",
          "affected_all": [
            "file"
          ],
          "file": "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
        }
      ]
    ```
  - **`folder`**

    Any file in an external folder that the Atmos component depends on was changed.

    Dependencies on external folders are defined using the `folder` attribute in the `settings.depends_on` map.

    For example:
    ```yaml title="stacks/catalog/terraform/top-level-component3.yaml"
    components:
      terraform:
        top-level-component3:
          metadata:
            component: "top-level-component1"
          settings:
            depends_on:
              1:
                file: "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
              2:
                folder: "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
    ```
    In the configuration above, we specify that the Atmos component `top-level-component3` depends on the folder
    `tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server`. If any file in the folder gets modified,
    the component `top-level-component3` will be included in the `atmos describe affected` command output.

    For example:
    ```json
      [
        {
          "component": "top-level-component3",
          "component_type": "terraform",
          "component_path": "components/terraform/top-level-component1",
          "stack": "tenant1-ue2-test-1",
          "stack_slug": "tenant1-ue2-test-1-top-level-component3",
          "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
          "affected": "folder",
          "affected_all": [
            "folder"
          ],
          "folder": "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
        }
      ]
    ```
  - **`deleted`**

    The component was deleted (exists in BASE but not in HEAD). This happens when a component is removed from
    a stack configuration. The component's `deleted` field will be set to `true` and `deletion_type` to `component`.

    For example, if you remove the `monitoring` component from the `prod-us-east-1` stack:
    ```json
      [
        {
          "component": "monitoring",
          "component_type": "terraform",
          "component_path": "components/terraform/monitoring",
          "stack": "prod-us-east-1",
          "stack_slug": "prod-us-east-1-monitoring",
          "affected": "deleted",
          "affected_all": [
            "deleted"
          ],
          "deleted": true,
          "deletion_type": "component"
        }
      ]
    ```
    Use this in CI/CD pipelines to trigger `terraform destroy` for removed components.
  - **`deleted.stack`**

    The entire stack was deleted. All components in the stack are marked as deleted with `deletion_type: stack`.

    For example, if you delete the entire `staging-us-west-2` stack:
    ```json
      [
        {
          "component": "vpc",
          "component_type": "terraform",
          "component_path": "components/terraform/vpc",
          "stack": "staging-us-west-2",
          "stack_slug": "staging-us-west-2-vpc",
          "affected": "deleted.stack",
          "affected_all": [
            "deleted.stack"
          ],
          "deleted": true,
          "deletion_type": "stack"
        },
        {
          "component": "eks",
          "component_type": "terraform",
          "component_path": "components/terraform/eks",
          "stack": "staging-us-west-2",
          "stack_slug": "staging-us-west-2-eks",
          "affected": "deleted.stack",
          "affected_all": [
            "deleted.stack"
          ],
          "deleted": true,
          "deletion_type": "stack"
        }
      ]
    ```
- **`affected_all`**

  Shows all component sections and attributes that were changed.

  For example, if you make changes to the `vars` and `settings` sections of the component `component-1` in the
  `nonprod` stack, and execute `atmos describe affected`, you will get the following result:
  ```json
    [
      {
        "component": "component-1",
        "component_type": "terraform",
        "stack": "nonprod",
        "stack_slug": "nonprod-component-1",
        "affected": "stack.vars",
        "affected_all": [
           "stack.vars",
           "stack.settings"
        ]
      }
    ]
  ```
  If you create a new Terraform/Tofu component, configure a new Atmos component `component-1` in the
  `nonprod` stack, and execute `atmos describe affected`, you will get the following result:
  ```json
  [
    {
      "component": "component-1",
      "component_type": "terraform",
      "stack": "nonprod",
      "stack_slug": "nonprod-component-1",
      "affected": "stack.metadata",
      "affected_all": [
        "component",
        "stack.metadata",
        "stack.vars",
        "stack.env",
        "stack.settings"
      ]
    }
  ]
  ```
  where:
  - **`affected`**

    Shows that the Atmos component's `metadata` section was changed
    (since the component is new and the `metadata` section is the first section that Atmos processes).
  - **`affected_all`**

    Shows all the affected sections and attributes:
    - **`component`**

      The Terraform component (Terraform configuration) was affected (since it was just added).
    - **`stack.metadata`**

      The Atmos component's `metadata` section was changed.
    - **`stack.vars`**

      The Atmos component's `vars` section was changed.
    - **`stack.env`**

      The Atmos component's `env` section was changed.
    - **`stack.settings`**

      The Atmos component's `settings` section was changed.

:::note

[Abstract Atmos components](/design-patterns/inheritance-patterns/abstract-component) (`metadata.type` is set to `abstract`)
are not included in the output since they serve as blueprints for other Atmos components and are not meant to be provisioned.

[Disabled Atmos components](/stacks/components#disabling-components-metadataenabled) (`metadata.enabled` is set to `false`)
are also not included in the output since they are explicitly disabled.

:::

## Output Example

```shell
[
  {
    "component": "infrastructure-tenant1",
    "component_type": "terraform",
    "component_path": "tests/fixtures/scenarios/complete/components/terraform/spacelift",
    "stack": "tenant1-ue2-prod",
    "stack_slug": "tenant1-ue2-prod-infrastructure-tenant1",
    "spacelift_stack": "tenant1-ue2-prod-infrastructure-tenant1",
    "atlantis_project": "tenant1-ue2-prod-infrastructure-tenant1",
    "affected": "stack.settings.spacelift.admin_stack_selector",
    "affected_all": [
        "stack.settings.spacelift.admin_stack_selector"
    ]
  },
  {
    "component": "infrastructure-tenant2",
    "component_type": "terraform",
    "component_path": "tests/fixtures/scenarios/complete/components/terraform/spacelift",
    "stack": "tenant2-ue2-prod",
    "stack_slug": "tenant2-ue2-prod-infrastructure-tenant2",
    "spacelift_stack": "tenant2-ue2-prod-infrastructure-tenant2",
    "atlantis_project": "tenant2-ue2-prod-infrastructure-tenant2",
    "affected": "stack.settings.spacelift.admin_stack_selector",
    "affected_all": [
      "stack.settings.spacelift.admin_stack_selector"
    ]
  },
  {
    "component": "test/test-component-override-2",
    "component_type": "terraform",
    "component_path": "components/terraform/test/test-component",
    "stack": "tenant1-ue2-dev",
    "stack_slug": "tenant1-ue2-dev-test-test-component-override-2",
    "spacelift_stack": "tenant1-ue2-dev-new-component",
    "atlantis_project": "tenant1-ue2-dev-new-component",
    "affected": "stack.vars",
    "affected_all": [
      "stack.vars"
    ]
  },
  {
    "component": "infra/vpc",
    "component_type": "terraform",
    "component_path": "components/terraform/infra/vpc",
    "stack": "tenant2-ue2-staging",
    "stack_slug": "tenant1-ue2-staging-infra-vpc",
    "spacelift_stack": "tenant1-ue2-staging-infra-vpc",
    "atlantis_project": "tenant1-ue2-staging-infra-vpc",
    "affected": "component",
    "affected_all": [
      "component"
    ]
  },
  {
    "component": "test/test-component-override-3",
    "component_type": "terraform",
    "component_path": "components/terraform/test/test-component",
    "stack": "tenant1-ue2-prod",
    "stack_slug": "tenant1-ue2-prod-test-test-component-override-3",
    "atlantis_project": "tenant1-ue2-prod-test-test-component-override-3",
    "affected": "stack.env",
    "affected_all": [
      "stack.env"
    ]
  },
  {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "file",
    "affected_all": [
      "file",
      "folder"
    ]
    "file": "tests/fixtures/scenarios/complete/components/terraform/mixins/introspection.mixin.tf"
  },
  {
    "component": "top-level-component3",
    "component_type": "terraform",
    "component_path": "components/terraform/top-level-component1",
    "stack": "tenant1-ue2-test-1",
    "stack_slug": "tenant1-ue2-test-1-top-level-component3",
    "atlantis_project": "tenant1-ue2-test-1-top-level-component3",
    "affected": "folder",
    "affected_all": [
      "file",
      "folder"
    ]
    "folder": "tests/fixtures/scenarios/complete/components/helmfile/infra/infra-server"
  }
]
```

## Affected Components with Dependencies

The output of the `atmos describe affected` command can include dependencies for the affected components.

If the command-line flag `--include-dependents=true` is passed to the `atmos describe affected` command, and there are
other components that depend on the affected components in the stack, the command will include a `dependents`
property (list) for each affected component. The `dependents` property is hierarchical - each component in the list will
also contain a `dependents` property if that component has dependent components as well.

For example, suppose that we have the following configuration for the Atmos components `component-1`, `component-2` and
`component-3` in the stack `plat-ue2-dev`:

**File:** `stacks/orgs/acme/plat/dev/us-east-2.yaml`

```yaml
components:
    terraform:
      component-1:
        metadata:
          component: "terraform-component-1"
        vars: {}

      component-2:
        metadata:
          component: "terraform-component-2"
        vars: {}
        settings:
          depends_on:
            1:
              component: "component-1"

      component-3:
        metadata:
          component: "terraform-component-3"
        vars: {}
        settings:
          depends_on:
            1:
              component: "component-2"
```

:::tip
For more details on how to configure component dependencies, refer to [`atmos describe dependents`](/cli/commands/describe/dependents)
:::

In the above configuration, `component-3` depends on `component-2`, whereas `component-2` depends on `component-1`.

If all the components are affected (modified) in the current working branch,
the `atmos describe affected --include-dependents=true` command will produce the following result:

```shell
[
   {
     "component": "component-1",
     "stack": "plat-ue2-dev",
     "stack_slug": "plat-ue2-dev-component-1",
     "included_in_dependents": false,
     "dependents": [
       {
         "component": "component-2",
         "stack": "plat-ue2-dev",
         "stack_slug": "plat-ue2-dev-component-2",
         "dependents": [
           {
             "component": "component-3",
             "stack": "plat-ue2-dev",
             "stack_slug": "plat-ue2-dev-component-3"
           }
         ]
       }
     ]
   },
   {
     "component": "component-2",
     "stack": "plat-ue2-dev",
     "stack_slug": "plat-ue2-dev-component-2",
     "included_in_dependents": true,
     "dependents": [
       {
         "component": "component-3",
         "stack": "plat-ue2-dev",
         "stack_slug": "plat-ue2-dev-component-3"
       }
     ]
   },
   {
     "component": "component-3",
     "stack": "plat-ue2-dev",
     "stack_slug": "plat-ue2-dev-component-3",
     "included_in_dependents": true
   }
 ]
```

The `component-1` component does not depend on any other component, and therefore it has the `included_in_dependents`
attribute set to `false`. The `component-2` and `component-3` components depend on other components and are included in
the `dependents` property of the other components, and hence the `included_in_dependents` attribute is set to `true`.

When processing the above output, you might decide to not plan/apply the `component-2` and `component-3` components
since they are in the `dependents` property of the `component-1` component. Instead, you might just
trigger `component-1` and then `component-2` and `component-3` in the order of dependencies.

## Detecting Deleted Components in Affected Stacks

The `atmos describe affected` command automatically detects components and stacks that exist in the BASE branch
but have been deleted in HEAD. This enables CI/CD pipelines to trigger `terraform destroy` for removed infrastructure.

### How it Works

When comparing HEAD (current branch) with BASE (target branch), Atmos not only detects modifications but also:

1. **Deleted components**: Components that exist in BASE but not in HEAD (within the same stack)
2. **Deleted stacks**: Entire stacks that exist in BASE but not in HEAD

Deleted components are marked with:

- `deleted: true` - Indicates the component was deleted
- `deletion_type: component` or `deletion_type: stack` - Specifies whether just the component or the entire stack was removed
- `affected: deleted` or `affected: deleted.stack` - The affected reason

### Filtering Deleted vs. Modified Components

Use `--query` or `jq` to separate deleted components from modified ones:

```shell
# Get only deleted components (for destruction)
atmos describe affected --query '[.[] | select(.deleted == true)]'

# Get only modified components (for apply)
atmos describe affected --query '[.[] | select(.deleted != true)]'

# Get deleted components in a specific stack
atmos describe affected --query '[.[] | select(.deleted == true and .stack == "prod-us-east-1")]'
```

### CI/CD Pipeline Example

Here's a GitHub Actions workflow that separates apply and destroy operations:

```yaml
jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      modified: ${{ steps.affected.outputs.modified }}
      deleted: ${{ steps.affected.outputs.deleted }}
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - uses: cloudposse/github-action-setup-atmos@v2

      - name: Detect affected
        id: affected
        run: |
          # Get all affected components
          atmos describe affected --format json > affected.json

          # Filter modified components (for apply)
          jq '[.[] | select(.deleted != true)]' affected.json > modified.json
          echo "modified=$(cat modified.json | jq -c)" >> $GITHUB_OUTPUT

          # Filter deleted components (for destroy)
          jq '[.[] | select(.deleted == true)]' affected.json > deleted.json
          echo "deleted=$(cat deleted.json | jq -c)" >> $GITHUB_OUTPUT

  apply:
    needs: detect-changes
    if: needs.detect-changes.outputs.modified != '[]'
    strategy:
      matrix:
        include: ${{ fromJson(needs.detect-changes.outputs.modified) }}
    steps:
      - uses: actions/checkout@v6
      - uses: cloudposse/github-action-setup-atmos@v2
      - run: atmos terraform apply ${{ matrix.component }} -s ${{ matrix.stack }}

  destroy:
    needs: detect-changes
    if: needs.detect-changes.outputs.deleted != '[]'
    environment: production  # Requires manual approval via GitHub Environment protection rules
    strategy:
      matrix:
        include: ${{ fromJson(needs.detect-changes.outputs.deleted) }}
    steps:
      # IMPORTANT: Check out the BASE branch (target branch), not HEAD.
      # The deleted component's configuration only exists in BASE.
      - uses: actions/checkout@v6
        with:
          ref: ${{ github.base_ref }}
      - uses: cloudposse/github-action-setup-atmos@v2
      - run: atmos terraform destroy ${{ matrix.component }} -s ${{ matrix.stack }} --auto-approve
```

:::warning Destroy Operations
Destruction of cloud resources is irreversible. The example above uses `--auto-approve` for automation but
includes `environment: production` to require manual approval via
[GitHub Environment protection rules](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-protection-rules).
Always review the list of deleted components before running `terraform destroy`.

**Important:** The destroy job must check out the **BASE branch** (target branch, e.g., `main`) rather than HEAD.
This is because `terraform destroy` needs access to the component's stack configuration and Terraform files,
which only exist in BASE — they've been deleted in HEAD.
:::

:::note Abstract Components
[Abstract components](/design-patterns/inheritance-patterns/abstract-component) (`metadata.type: abstract`)
are not reported as deleted since they are blueprints and are not provisioned.
:::

## Working with Private Repositories

There are a few ways to work with private repositories with which the current local branch is compared to detect the changed files and affected Atmos
stacks and components:

- Using the `--ssh-key` flag to specify the filesystem path to a PEM-encoded private key to clone private repos using SSH, and
  the `--ssh-key-password` flag to provide the encryption password for the PEM-encoded private key if the key contains a password-encrypted PEM block

- Execute the `atmos describe affected --repo-path <path_to_cloned_target_repo>` command in a [GitHub Action](https://docs.github.com/en/actions).
  For this to work, clone the remote private repository using the [checkout](https://github.com/actions/checkout) GitHub action. Then use
  the `--repo-path` flag to specify the path to the already cloned target repository with which to compare the current branch

- It should just also work with whatever SSH config/context has been already set up, for example, when
  using [SSH agents](https://www.ssh.com/academy/ssh/agent). In this case, you don't need to use the `--ssh-key`, `--ssh-key-password`
  and `--repo-path` flags to clone private repositories

## Using with GitHub Actions

If the `atmos describe affected` command is executed in a [GitHub Action](https://docs.github.com/en/actions), and you don't want to store or
generate a long-lived SSH private key on the server, you can do the following (**NOTE:** This is only required if the action is attempting to clone a
private repo which is not itself):

- Create a GitHub
  [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
  with scope permissions to clone private repos

- Add the created PAT as a repository or GitHub organization [secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets)

- In your GitHub action, clone the remote repository using the [checkout](https://github.com/actions/checkout) GitHub action

- Execute `atmos describe affected` command with the `--repo-path` flag set to the cloned repository path using
  the [`GITHUB_WORKSPACE`](https://docs.github.com/en/actions/learn-github-actions/variables) ENV variable (which points to the default working
  directory on the GitHub runner for steps, and the default location of the repository when using the [checkout](https://github.com/actions/checkout)
  action). For example:

  ```shell
  atmos describe affected --repo-path $GITHUB_WORKSPACE
  ```

## Upload the affected components and stacks to an HTTP endpoint

If the `--upload=true` command-line flag is passed, Atmos will upload the affected components and stacks to a
specified HTTP endpoint.

The endpoint can process the affected components and their dependencies in a CI/CD pipeline (e.g. execute
`terraform apply` on all the affected components in the stacks and all the dependencies).

Atmos will perform an HTTP POST request to the URL `${ATMOS_PRO_BASE_URL}/${ATMOS_PRO_ENDPOINT}`, where the base URL
is defined by the `ATMOS_PRO_BASE_URL` environment variable, and the URL path is defined by the `ATMOS_PRO_ENDPOINT`
environment variable.

An [Authorization](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) header
`Authorization: Bearer $ATMOS_PRO_TOKEN` will be added to the HTTP request (if the `ATMOS_PRO_TOKEN` environment
variable is set) to provide credentials to authenticate with the server.

:::note
If the `--upload=true` command-line flag is passed, the `--include-dependencies` and `--include-settings` flags are
automatically set to `true`, so the affected components will be uploaded with their dependencies and settings
(if they are configured in Atmos stack manifests).
:::

The payload of the HTTP POST request will be a JSON object with the following schema:

```shell
{
     "base_sha": "6746ba4df9e87690c33297fe740011e5ccefc1f9",
     "head_sha": "5360d911d9bac669095eee1ca1888c3ef5291084",
     "repo_url": "https://github.com/cloudposse/atmos",
     "repo_host": "github.com",
     "repo_name": "atmos",
     "repo_owner": "cloudposse",
     "stacks": [
        {
          "component": "vpc",
          "component_type": "terraform",
          "component_path": "examples/quick-start-advanced/components/terraform/vpc",
          "stack": "plat-ue2-dev",
          "stack_slug": "plat-ue2-dev-vpc",
          "affected": "stack.vars",
          "included_in_dependents": false,
          "dependents": [],
          "settings": {}
        }
    ]
 }
```

where:

- **`base_sha`**

  the Git commit SHA of the base branch against which the changes in the current commit are compared
- **`head_sha`**

  the SHA of the current Git commit
- **`repo_url`**

  the URL of the current repository
- **`repo_name`**

  the name of the current repository
- **`repo_owner`**

  the owner of the current repository
- **`repo_host`**

  the host of the current repository
- **`stacks`**

  a list of affected components and stacks with their dependencies and settings
