# Planning Terramate stacks in Atlantis without generated code

> Atlantis plans exactly the stacks a change affects, even with no generated code in Git.

Engineering note by Oleksandr Ponomarov · 12 November 2026 · Topics: terraform, terramate, aws, atlantis
Canonical: https://oponomarov.com/blog/posts/planning-terramate-stacks-in-atlantis-without-generated-code/

This is the second of two posts on running
[Terramate](https://terramate.io/docs/cli/) stacks through
[Atlantis](https://www.runatlantis.io/) with
[terramate-atlantis-config](https://github.com/devops-roast/terramate-atlantis-config).
If you're just joining, the
[first post](/blog/posts/generating-atlantis-config-from-terramate-stacks/)
introduces the tool and the terms used here. It ended with Atlantis
generating `atlantis.yaml` on every pull request, while Terramate's own
generated Terraform stayed committed. This post removes that too, follows
one change to a global through both tools to show what breaks, and then
works through the fix, the ordering of applies, and the corner cases that
sit between the two tools.

> [!NOTE] Series and demo
>
> The examples come from
> [terramate-atlantis-autoplan](https://github.com/shmileee/terramate-atlantis-autoplan),
> a companion to the first post's demo with no generated code in Git at
> all. Each scenario has a pull request showing Atlantis' real response.
> Tool versions are pinned in its
> [`mise.toml`](https://github.com/shmileee/terramate-atlantis-autoplan/blob/31b409198315a6dbff0da2bf3488a7857b0dae95/mise.toml)
> and
> [`atlantis/Dockerfile`](https://github.com/shmileee/terramate-atlantis-autoplan/blob/31b409198315a6dbff0da2bf3488a7857b0dae95/atlantis/Dockerfile),
> and the transcripts were run with those pins.

## Why commit generated code at all?

It's worth asking the question honestly before answering it, because
committing generated code is not a mistake. Terramate's `generate_hcl`
blocks turn globals into Terraform, and committing the result means a
reviewer sees exactly the Terraform that will run. It also means the
repository plans as-is, with no generation step anywhere in the pipeline.

The cost appears as the repository grows. Change one global in an account
directory and the generated code changes in every stack below it: one
meaningful line in review, and a page of mechanical ones. In practice it
is easy to end up skimming the generated files, at which point the noise is
still being paid for but the review is no longer happening.

There is a less obvious reason the generated files matter, though, and it
is the one this post is really about. In a repository that Atlantis runs,
those files are what tell Atlantis a stack was affected. Take them away and
the signal goes with them.

## The global change nobody planned

The demo has two AWS accounts and one stack outside them, `shared/settings`:

```text
config/settings.json
modules/shared/main.tf
stacks/
├── aws/
│   ├── aws.tm.hcl
│   ├── acme-development/
│   │   ├── account.tm.hcl
│   │   └── eu-west-1/
│   │       ├── region.tm.hcl
│   │       ├── app/
│   │       └── network/
│   └── acme-production/
│       └── ... the same shape ...
├── imports/
│   ├── aws.tm.hcl
│   └── common.tm.hcl
└── shared/
    ├── shared.tm.hcl
    └── settings/
```

Each account directory sets an `account_name` global, which the stacks read
through generated Terraform. The
[first pull request](https://github.com/shmileee/terramate-atlantis-autoplan/pull/1)
changes that global in the development account, and that is its only file:

```text
$ git diff --stat main
 stacks/aws/acme-development/account.tm.hcl | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
```

Two stacks are affected, the development `app` and `network`, since both
read `account_name`. With committed generated code, their
`_terramate_generated_aws.tf` files would have changed too, and the
tool's default `when_modified` patterns (`*.tf`, `*.tf.json` and
`.terraform.lock.hcl`, all relative to the stack's own directory) would
have matched them. With no generated code in Git, the pull request touches
one `.tm.hcl` file two directories above the stacks, and no project's
patterns match it. Atlantis plans nothing.

The natural next question is whether Terramate's own change detection could
help. The tool has a `--changed` flag that keeps only the stacks Terramate
considers changed, so we could generate projects only for those. On the
same branch:

```text
$ terramate list --changed
$ terramate-atlantis-config generate --changed --output - --no-header
version: 3
automerge: false
parallel_plan: true
parallel_apply: true
projects: []
```

`terramate list --changed` prints nothing. Its
[change detection](https://terramate.io/docs/cli/change-detection/) maps a
changed file _up_ to the stack that contains it, and follows a stack's
modules and `watch` files, but it does not map a changed parent config file
_down_ to the stacks that inherit from it:

<figure class="concept-diagram" data-concept-diagram>
  <div class="diagram-exhibit" data-exhibit role="img"
       aria-label="Asked which stack holds account.tm.hcl, the answer is none. Asked which stacks read it, the answer is the development app and network stacks.">
    <div class="diagram-exhibit-label">WHICH STACK HOLDS IT?</div>
    <div>
      <span>account.tm.hcl</span><i aria-hidden="true">→</i>
      <span>none</span>
    </div>
    <div class="diagram-exhibit-label diagram-exhibit-label-secondary">WHICH STACKS READ IT?</div>
    <div>
      <span>account.tm.hcl</span><i aria-hidden="true">→</i>
      <span>app</span>
      <span>network</span>
    </div>
  </div>
  <figcaption class="exhibit-caption">Both tools ask the first question about the development account's account.tm.hcl. The change needs the second, because the file sits above the stacks.</figcaption>
</figure>

Atlantis and Terramate share the same blind spot here: both decide what a
change affects by where the changed file sits, not by what reads it.

## Telling Atlantis what each stack reads

If neither tool can infer the relationship at plan time, it has to be
written down where Atlantis will look, and the only place Atlantis looks is
each project's `when_modified`. So the question becomes: what, besides its
own directory, does a stack read? For Terramate there are four answers, and
the demo has one of each:

| A stack reads | In the demo | Listed in `when_modified` as |
| --- | --- | --- |
| `.tm` and `.tm.hcl` files in its directory and every parent | `account.tm.hcl` | two globs per directory |
| files pulled in by `import` blocks, transitively | `stacks/imports/aws.tm.hcl` | each file's exact path |
| local modules it calls, transitively | `modules/shared` | `<module dir>/**` |
| files in its `watch` attribute | `config/settings.json` | each file's exact path |

`when_modified_from_config` adds each stack's inputs of all four kinds to
its project. In the demo it is switched on in the tool's config file,
alongside the ordering settings we'll return to later:

```yaml title=".terramate-atlantis.yaml"
output: atlantis.yaml

# Add the Terramate files each stack depends on to its
# when_modified, so a change outside the stack still plans it.
when_modified_from_config: true

# Turn each stack's `after` into Atlantis ordering.
depends_on: true
execution_order_groups: true
```

Here is what it produces for the development `network` stack, which sits
five directories deep:

```yaml
  - name: aws-acme-development-eu-west-1-network
    dir: stacks/aws/acme-development/eu-west-1/network
    autoplan:
      enabled: true
      when_modified:
        - '*.tf'
        - '*.tf.json'
        - .terraform.lock.hcl
        - '*.tm.hcl'
        - '*.tm'
        - ../*.tm.hcl
        - ../*.tm
        - ../../*.tm.hcl
        - ../../*.tm
        - ../../../*.tm.hcl
        - ../../../*.tm
        - ../../../../*.tm.hcl
        - ../../../../*.tm
        - ../../../../../*.tm.hcl
        - ../../../../../*.tm
        - ../../../../imports/aws.tm.hcl
        - ../../../../imports/common.tm.hcl
```

The first three entries are the defaults. Then come two globs per
directory, from the stack up to the repository root, and finally the two
files its config imports. Relative paths are hard to read in a list like
this, so here is the directory each `.tm.hcl` glob resolves to, counting
up from the stack, next to the file it would match today:

| Entry | Directory | Matches today |
| --- | --- | --- |
| `*.tm.hcl` | `network/`, the stack | `stack.tm.hcl` |
| `../*.tm.hcl` | `eu-west-1/` | `region.tm.hcl` |
| `../../*.tm.hcl` | `acme-development/` | `account.tm.hcl` |
| `../../../*.tm.hcl` | `aws/` | `aws.tm.hcl` |
| `../../../../*.tm.hcl` | `stacks/` | nothing yet |
| `../../../../../*.tm.hcl` | the repository root | `terramate.tm.hcl` |

The third row is the one that matters for the first pull request: it
matches the changed `account.tm.hcl`. The production stacks' equivalent
entry resolves to `stacks/aws/acme-production/`, which doesn't, and neither
does any entry of `shared/settings`. That is exactly the selection we
wanted, and it is what Atlantis did:

```text
Ran Plan for 2 projects:

1. project: `aws-acme-development-eu-west-1-network` dir: `stacks/aws/acme-development/eu-west-1/network` workspace: `default`
1. project: `aws-acme-development-eu-west-1-app` dir: `stacks/aws/acme-development/eu-west-1/app` workspace: `default`
```

Notice the design choice: the tool still generates every project, and
leaves the selection to Atlantis' ordinary file matching. Nothing about
which stacks to plan is decided when the file is generated, which matters
when a pull request keeps changing after it is opened.

The other three kinds of input behave the same way. Each of the demo's
next three pull requests changes a single file outside every stack:

| Pull request | Changes only | Is planned for |
| --- | --- | --- |
| [#1](https://github.com/shmileee/terramate-atlantis-autoplan/pull/1) | the development `account.tm.hcl` | the two development stacks |
| [#2](https://github.com/shmileee/terramate-atlantis-autoplan/pull/2) | `stacks/imports/aws.tm.hcl` | the four AWS stacks, which import it |
| [#3](https://github.com/shmileee/terramate-atlantis-autoplan/pull/3) | `modules/shared/main.tf` | the two `app` stacks, which call it |
| [#4](https://github.com/shmileee/terramate-atlantis-autoplan/pull/4) | `config/settings.json` | `shared/settings`, which lists it in `watch` |

The imported file is the subtle one. `stacks/aws/aws.tm.hcl` imports
`stacks/imports/aws.tm.hcl`, so it shapes every stack under `stacks/aws/`,
but `shared/shared.tm.hcl` imports only `common.tm.hcl`. The tool lists an
imported file only in the projects whose config actually imports it, so
`shared/settings` stays unplanned. The `watch` case is the mirror image:
only `shared/settings` declares it, so only `shared/settings` gets the
entry:

```hcl title="stacks/shared/settings/stack.tm.hcl"
stack {
  name  = "settings"
  watch = ["/config/settings.json"]
  id    = "4d15ed0d-649f-44ad-ae3e-6c9b398ae82e"
}
```

## Globs for config, exact paths for the rest

You might wonder why config files appear as globs while imports appear as
exact paths. The answer comes from what Terramate itself reads. It reads
_every_ `.tm` and `.tm.hcl` file in a stack's directory and its parents, so
a pair of globs per directory matches exactly that set, including a file a
pull request adds or deletes, and the generated file doesn't change when
someone adds a config file. That is why `../../../../*.tm.hcl` is listed
even though `stacks/` holds no config today: the day someone adds one, it is
already covered. An import, on the other hand, usually names one file, so it
is listed as one file; a glob there would plan stacks for changes to files
they never read.

The module entries are the exception in the other direction. The two `app`
stacks call the shared module, and their lists end with:

```yaml
        - ../../../../../modules/shared/**
```

That matches any file in the module directory, not only `.tf` files. It
mirrors Terramate's change detection, and it covers templates or policy
files that a module reads with `file()`. Modules called from a module are
followed too, so a change two modules deep still plans the stacks at the
top. Remote module sources, from a registry or Git, are ignored; a version
bump to one of those is an edit to a `.tf` file in the stack or to the
config that generates it, both of which are already covered.

## Two hooks, in order

Without generated code in Git, Atlantis has to generate it before it can
plan anything, so the server-side config now runs two hooks:

```yaml title="atlantis/repos.yaml"
repos:
  - id: /.*/
    pre_workflow_hooks:
      - run: terramate generate
        description: terramate generate
      - run: terramate-atlantis-config generate --output atlantis.yaml
        description: terramate-atlantis-config
```

Each pull request event now passes through both before Atlantis reads its
config:

<figure class="concept-diagram" data-concept-diagram>
  <div class="diagram-exhibit" data-exhibit role="img"
       aria-label="First, the two pre-workflow hooks write the stacks' Terraform and then atlantis.yaml. Then Atlantis matches the pull request's files against each project's when_modified and autoplans the matches.">
    <div class="diagram-exhibit-label">FIRST · THE HOOKS WRITE</div>
    <div>
      <span>terraform</span><i aria-hidden="true">→</i>
      <span>atlantis.yaml</span>
    </div>
    <div class="diagram-exhibit-label diagram-exhibit-label-secondary">THEN · ATLANTIS</div>
    <div>
      <span>matches files</span><i aria-hidden="true">→</i>
      <span>autoplans</span>
    </div>
  </div>
  <figcaption class="exhibit-caption">terramate generate writes the Terraform first, because atlantis.yaml is derived partly from it.</figcaption>
</figure>

The order is not arbitrary. The tool finds module calls by reading the
`.tf` files that exist when it runs, and in this demo the module call is in
a hand-written `main.tf`, but in many Terramate repositories it is in
generated code.

> [!IMPORTANT] Generate Terraform before the project list
>
> If a module is called from generated code and `terramate generate` runs
> second, the tool finds no call, and that module silently drops out of
> every `when_modified`. Nothing fails; a change to the module simply stops
> planning the stacks that use it.

Running the first hook also means the Atlantis image now needs the
`terramate` CLI, which it didn't before, at the same version the
repository was written for.

The repository's
[`.gitignore`](https://github.com/shmileee/terramate-atlantis-autoplan/blob/31b409198315a6dbff0da2bf3488a7857b0dae95/.gitignore)
then excludes both kinds of generated file, `_terramate_generated_*.tf` and
`atlantis.yaml`, so the
[sixth pull request](https://github.com/shmileee/terramate-atlantis-autoplan/pull/6)
can add a whole new stack with just two files, its `stack.tm.hcl` and
`main.tf`, and still be planned.

## Applying stacks in order

The config file above also switched on two ordering settings, and they
solve a separate problem. In the demo, each account's `app` stack declares
that it runs after `network`:

```hcl title="stacks/aws/acme-development/eu-west-1/app/stack.tm.hcl"
stack {
  name  = "app"
  after = ["../network"]
  id    = "bd6801db-c87e-48f5-beb9-79580387fc14"
}
```

`terramate run` honors that ordering, but Atlantis knows nothing about it.
`depends_on` turns it into Atlantis' own `depends_on`, which lists project
names, and `execution_order_groups` assigns each project the length of its
longest chain of dependencies:

```yaml
  - name: aws-acme-development-eu-west-1-app
    dir: stacks/aws/acme-development/eu-west-1/app
    execution_order_group: 1
    depends_on:
      - aws-acme-development-eu-west-1-network
```

A project with no dependencies is in group `0`, which the tool expresses by
leaving the key out. Across the whole demo, that gives two groups:

<figure class="concept-diagram" data-concept-diagram>
  <div class="diagram-exhibit" data-exhibit role="img"
       aria-label="Execution order group 0 holds the development network, production network and shared-settings projects. Group 1 holds the development app and production app projects, each depending on the network project in its own account.">
    <div class="diagram-exhibit-label">GROUP 0 · NO DEPENDENCIES</div>
    <div>
      <span>development network</span>
      <span>production network</span>
      <span>shared-settings</span>
    </div>
    <div class="diagram-exhibit-label diagram-exhibit-label-secondary">GROUP 1 · AFTER NETWORK</div>
    <div>
      <span>development app</span>
      <span>production app</span>
    </div>
  </div>
  <figcaption class="exhibit-caption">Atlantis runs group 0 before group 1, and each app waits for its own account's network; with parallel plan and apply on, as here, a group's projects run together.</figcaption>
</figure>

The two settings do different jobs, and it helps to keep them apart.
Execution order groups decide the order in which Atlantis runs the
projects of one command. `depends_on` decides whether a project may be
applied _at all_ before the projects it names. The
[fifth pull request](https://github.com/shmileee/terramate-atlantis-autoplan/pull/5)
changes both stacks and then tries to apply `app` on its own first, which
only `depends_on` can refuse:

```text
**Apply Failed**: Can't apply your project unless you apply its dependencies: [aws-acme-development-eu-west-1-network]
```

A plain `atlantis apply` afterwards applies `network`, then `app`.

Because `depends_on` matches project names, it needs them: with
`--create-project-name=false` the tool fails with an error rather than
produce entries Atlantis can't match. And `after` and `before` resolve the
same way they do for `terramate run`, so an entry such as `tag:network` or
a directory that isn't itself a stack expands to every stack it covers.

## Running Terraform through `terramate run`

Neither demo needs it, but there is one more thing the tool can generate
that belongs in a post about advanced use: an Atlantis workflow that runs
Terraform through `terramate run` instead of calling it directly. The
argument for it is that Terramate's own checks, its _safeguards_, then
apply inside Atlantis. Adding `--generate-workflows` to the demo gives
every project `workflow: terramate` and appends this block:

```yaml
workflows:
  terramate:
    apply:
      steps:
        - run: terramate run --no-recursive --disable-safeguards=git -- terraform apply "$PLANFILE"
    plan:
      steps:
        - run: terramate run --no-recursive --disable-safeguards=git -- terraform init -input=false > /dev/null
        - run: terramate run --no-recursive --disable-safeguards=git -- terraform plan -input=false -out="$PLANFILE"
```

Each part of those commands is there for a reason, and the
[workflow documentation](https://github.com/devops-roast/terramate-atlantis-config/blob/80324c4dbdb4be354760c6bfa12444d684ae9877/docs/workflows.md)
explains them:

| Part | Why it's there |
| --- | --- |
| `--no-recursive` | each stack is its own project, so a parent must not also run its children |
| `--disable-safeguards=git` | a pull request branch is usually behind `main`, which Terramate's `git-out-of-sync` check refuses |
| `> /dev/null` on `init` | GitHub colors `- Installing ...` lines red in Atlantis' diff blocks; errors still reach stderr |

Without the second flag, the plan fails with
`current HEAD is out-of-date with the remote base branch`, which reads like
an Atlantis problem but is Terramate's. The `outdated-code` safeguard stays
on, and it is the one most worth having, though not in this post's setup.
Here the first hook has just generated the code, so the check always
passes. In the first post's setup, with generated code committed, it is
what refuses to plan a commit that forgot to regenerate.

Three costs come with it. A top-level `workflows` block needs
`allow_custom_workflows: true` on the server, alongside
`allowed_overrides: [workflow]`. A project's `terraform_version` no longer
picks the binary, because a `run` step uses whatever `terraform` is on the
server's `PATH`, and Atlantis' version switching only applies to its
built-in steps. And the plan output is now one layer further from Terraform,
which matters to the person reading a failed plan. I'd treat it as worth it
when Terramate's safeguards are something you rely on elsewhere, and
unnecessary when they aren't.

## Corner cases

The mechanism above is simple, but it sits between two tools with their own
rules, and a few cases behave in ways that are worth knowing before they
surprise you.

### Project names move with the tree

The first post's demo named its projects `acme-development-eu-west-1-app`;
this one names the same stack `aws-acme-development-eu-west-1-app`. Nothing
was configured differently. The default `auto-strip` strategy removes the
path prefix that _all_ stacks share, and the `shared/settings` stack shrinks
that prefix:

| Repository | Shared prefix | Development `app` is named |
| --- | --- | --- |
| only AWS stacks | `stacks/aws/` | `acme-development-eu-west-1-app` |
| plus `shared/settings` | `stacks/` | `aws-acme-development-eu-west-1-app` |
| either, with the prefix pinned | `stacks/aws/`, fixed | `acme-development-eu-west-1-app` |

Adding one stack outside the shared prefix therefore renames every existing
project, and any `atlantis plan -p` command, script or runbook that names a
project by its old name stops matching. With `depends_on` switched on, the
rename is at least consistent, because the entries are regenerated from the
same names, but anything outside the file is not. Filters don't have this
effect, since names are always computed against every stack, but changes to
the tree do.

Pinning the prefix makes the names independent of the tree:

```text
$ terramate-atlantis-config generate --output - --no-header \
    --project-name-prefix stacks/aws | grep 'name:'
  - name: acme-development-eu-west-1-app
  - name: acme-development-eu-west-1-network
  - name: acme-production-eu-west-1-app
  - name: acme-production-eu-west-1-network
  - name: stacks-shared-settings
```

The stack outside the prefix keeps its full path, which is a reasonable
price for names that only change when you change them.

### Don't combine `when_modified_from_config` with `--changed`

It's tempting to use both flags: `--changed` to keep the file small, and
`when_modified_from_config` to catch the indirect changes. But `--changed`
removes projects before Atlantis ever sees them, and as the transcript
earlier showed, it removes the very stacks affected by a parent config
change. Their carefully derived `when_modified` never gets a chance to
match.

If you do use `--changed`, for instance with a committed file and a
repository that never changes globals, two details of its comparison are
worth knowing. The base ref has to exist in the checkout, which a shallow
or single-branch clone (the default of `actions/checkout`, and of Atlantis'
`branch` checkout strategy) doesn't provide. And the comparison is direct,
not from the merge base, so if `main` has moved on since the branch was cut,
stacks changed only on `main` count as changed too. Passing the merge base
avoids the second:

```bash
terramate-atlantis-config generate --changed \
  --changed-base-ref "$(git merge-base origin/main HEAD)"
```

### Globals that override the derived list

Stack globals from the first post interact with the flag, and not all in
the same way:

| The stack sets | Derived entries | The project's `when_modified` |
| --- | --- | --- |
| none of them | added | defaults, then derived |
| `atlantis_extra_deps` | added | defaults, extra, then derived |
| `atlantis_when_modified` | not added | exactly the global's list |
| `atlantis_autoplan = false` | added | written, but not autoplanned |

`atlantis_extra_deps` is the right tool for a file inside a stack directory
that the defaults miss. Terramate counts any file in a stack's directory,
such as `files/policy.json`, as a change to it, while the tool's default
patterns only match Terraform files. `atlantis_when_modified` is the
blunter instrument: a stack that sets it opts out of everything above,
including the parent config globs, and is back to the problem this post
started with.

### Nested stacks

Terramate allows a stack inside another stack's directory. The nested one
inherits the parent's globals, so its project gets the parent stack's
config files as well as its own. The reverse isn't true: a change in the
child's directory does not plan the parent, because Terramate doesn't apply
the child's config to it. This matches what each stack actually reads,
which is the right test for every entry in the list.

### Ordering through stacks that aren't generated

A stack that is filtered out, skipped or unchanged isn't a project, so it
can't appear in `depends_on`. Ordering is followed _through_ it, as
`terramate run` does. Take three stacks where `c` runs after `b`, and `b`
runs after `a`:

<figure class="concept-diagram" data-concept-diagram>
  <div class="diagram-exhibit" data-exhibit role="img"
       aria-label="Terramate runs a, then b, then c. When b isn't generated, Atlantis gets only the projects a and c, and c depends on a.">
    <div class="diagram-exhibit-label">TERRAMATE · RUN ORDER</div>
    <div>
      <span>a</span><i aria-hidden="true">→</i>
      <span>b</span><i aria-hidden="true">→</i>
      <span>c</span>
    </div>
    <div class="diagram-exhibit-label diagram-exhibit-label-secondary">ATLANTIS · B NOT GENERATED</div>
    <div>
      <span>a</span><i aria-hidden="true">→</i>
      <span>c</span>
    </div>
  </div>
  <figcaption class="exhibit-caption">With b left out, the project for c lists a in its depends_on, so c still waits for everything b waited for.</figcaption>
</figure>

A dependency cycle, which Terramate would also refuse to order, becomes a
warning, and the stacks on it share an execution order group. With
`depends_on` on, Atlantis can't apply those projects until the cycle is
broken, since each waits for the other.

### What the tool can't see

Some inputs are invisible to a tool that reads files at hook time. Module
calls in `.tf.json` files aren't read, matching Terramate's own change
detection. An import with a glob source, such as `/imports/*.tm.hcl`,
lists the files that exist when the tool runs, so a pull request that only
deletes one of them matches nothing. And a module outside the repository
root is skipped with a warning rather than an error, since Atlantis
couldn't match a pull request's files against it anyway:

```text
Warning: when_modified from config: skipping module "../../outside" called from one/main.tf: it is outside the root directory
```

That warning comes from the tool's
[autoplan documentation](https://github.com/devops-roast/terramate-atlantis-config/blob/80324c4dbdb4be354760c6bfa12444d684ae9877/docs/autoplan.md)
rather than from the demo, which has no such module. As with the warnings
in the first post, when the hook runs it, it appears only on the hook's job
page in Atlantis, not in a pull request comment.

### The size of the file

Each project gets two entries per directory between the stack and the root,
so the five-deep stack above carries twelve config entries before its
imports and modules. With the file generated on the server nobody reads it,
so this costs little. If you commit `atlantis.yaml` instead, expect it to
grow, and expect it to change whenever an import, `watch` list or module
call does.

## Limitations

The approach has one deliberate bias: it plans too much rather than too
little. A glob over an account directory's config files can't tell a change
to a global from a change to a comment, or a global every stack reads from
one none of them does, so any edit there plans every stack below it. For a
tool that decides whether infrastructure changes get reviewed, that seems
like the right direction to err in, but it does mean noisy plans for
cosmetic edits.

It also moves work onto the Atlantis server. The image needs `terramate`
and the tool, both pinned, and both generation steps now run in Atlantis'
checkout of every pull request. The change-detection behavior described
above was observed with the Terramate version the demo pins, and the
tool's documentation reports the same for the release after it; a future
Terramate release could map parent changes down to stacks, which would
make `--changed` a viable alternative.

## Where to from here?

We've followed one global change from a quiet non-event to a precise plan,
and the underlying rule is the one from the first post, applied one level
deeper: anything Atlantis needs to know should be derived from what
Terramate already reads, rather than maintained beside it. The ordering
settings and the generated workflow are the same rule again, applied to
`after` and to Terramate's safeguards.

The demo repository is set up to be forked, and its pull requests are a
good place to test a change to your own layout before it meets a real one.
If your repository has a kind of input none of the four rows above
describes, that is the case I'd most like to hear about.
