Terramate Change Detection: Why Stacks Get Skipped (or Flagged) and How to Diagnose It
When 'terramate run --changed' skips stacks that should have changed (or flags ones that shouldn't), the cause is usually git state — shallow clones, wrong base branches, or undeclared module relationships. A diagnostic guide with ordered checks and fixes.
08 Apr 2026, 01:22 UTC

The symptom
You edited Terraform files, pushed the branch, and the pipeline ran — but terramate run --changed deployed nothing. Or the opposite: stacks you never touched got planned and applied. In both cases the root cause is almost never Terraform. It is how Terramate decides what "changed" means, and that decision depends on git state that is easy to get wrong, especially in CI.
The useful takeaway: change detection is a comparison between two git refs plus a set of file-ownership rules. When it misbehaves, you can diagnose it in minutes by checking the refs, the checkout depth, and the stack configuration — in that order.
Everything below assumes a recent Terramate CLI. The project is actively developed, so flag names and config block syntax can shift between releases. Run terramate version and confirm behavior against your installed version's terramate --help output before changing pipeline config.
Cause map: symptom to likely cause
| What you observe | Most likely cause |
|---|---|
Empty --changed result in CI, correct result locally | Shallow clone or missing base branch — the merge-base cannot be computed |
| Edits to a shared module don't mark any stack changed | Module lives outside the stack directory and no watch/wants relationship is declared |
| Local and CI results differ on the same commit | Uncommitted or untracked files locally; detection compares working tree vs. committed diff differently |
| Wrong stacks flagged on every run | Configured default base branch (main vs master vs trunk) doesn't match the real one |
Stacks missing from terramate list entirely | Exclusion or filtering issue — not a change-detection problem at all |
Ordered checks
Work through these in order; each is cheap and eliminates a whole class of causes.
- Confirm the stacks exist and aren't excluded. Run
terramate listfrom the repository root. If a stack is missing here, change detection is irrelevant — fix stack discovery or exclusion rules first. - See which refs Terramate compares. Run
terramate list --changed --log-level=debug. The debug output shows the git refs used for the comparison. If the base ref is not what you expect, the problem is configuration, not git history. - Verify the merge-base resolves. Run
git merge-base HEAD origin/<base-branch>in the same environment where detection fails (including inside the CI job). If this errors or returns nothing, Terramate has no valid comparison point. Also rungit rev-parse --is-shallow-repository—truein CI is the classic failure. - Inspect stack relationships. Open the affected stack's
stack.tm.hcland look forwatchpaths orwants/wanted_bydeclarations. By default, a stack is only "changed" when files inside its own directory change. Shared modules outside the stack directory are invisible unless you declare the relationship. - Isolate with a trivial edit. Make a one-line change to a file inside the stack directory, commit it on a branch, and re-run
terramate list --changed. If the stack is now flagged, detection works and the issue is relationship configuration. If it still isn't flagged, the git comparison itself is broken.
Fixes tied to each finding
Shallow checkout or missing base branch in CI
Several CI checkout actions default to a shallow clone with history depth 1 and only the triggering branch. Terramate then has no merge-base and detection silently misbehaves. The fix is to fetch full history and the base branch. With the common GitHub checkout action, that looks like:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch base branch
run: git fetch origin main:mainRun this in your CI job with whatever permissions the checkout step already has. fetch-depth: 0 pulls full history, which is slower on large repos — if that cost matters, a deeper-but-bounded depth can work, but verify the merge-base resolves. Confirm the fix by printing git merge-base HEAD origin/main in the job before invoking Terramate.
Shared modules not triggering stacks
Declare the dependency explicitly. In the stack's config, add a watch list pointing at the shared module path, or use wants/wanted_by to express stack-to-stack relationships so a change in a "base" stack marks dependents as changed. The exact syntax varies by version — check terramate --help and the version-pinned docs rather than copying snippets from older blog posts. After adding the declaration, repeat the trivial-edit test against a file in the shared module.
Uncommitted or untracked files skewing local results
Change detection treats the working tree differently from committed history, so a local run with uncommitted edits will not match a CI run on the pushed commit. Before comparing local and CI behavior, commit or stash your changes so both environments evaluate the same git state.
Wrong base branch configured
If your repository's default branch is trunk but Terramate (or your pipeline) compares against main, every comparison is against the wrong target. Align the configured default base branch in the Terramate config with the actual repository default, and make sure CI fetches that same branch.
Filtering stacked on top of broken detection
If you combine --changed with --tags filters, a detection bug looks like a tagging bug. Only layer tag filters on after you have confirmed terramate list --changed alone returns the expected set.
A minimal local reproduction
To confirm expected behavior independent of your real repo, build a scratch case:
- Create a fresh git repo, commit a Terramate config, and define two stacks,
stacks/aandstacks/b. - Commit that as your base, then create a branch and edit one file inside
stacks/aonly. - Run
terramate list --changed. Expect onlystacks/ain the output. - Now edit a shared file outside both stacks. Expect no stacks flagged until you add a
watchdeclaration.
If the scratch repo behaves as expected but your real repo doesn't, diff the two configurations and git states. If even the scratch repo misbehaves, capture the version and debug logs — that is escalation material.
When to escalate
Two situations justify going beyond local diagnosis:
- Detection is correct locally but wrong in CI even after a full fetch and a verified merge-base. That points to environment or version differences, not checkout depth.
- Debug logs show unexpected ref resolution — Terramate comparing refs you never configured. That suggests version-specific behavior worth checking against the project's changelog and issue tracker for your installed release.
When you escalate, include the output of terramate version, the debug log lines showing the compared refs, and the results of the merge-base and shallow-repository checks. That turns a vague "detection is broken" report into something actionable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.