Skip to content

Plan and apply

The lifecycle

  1. Open a pull request touching one or more units. The engine plans every impacted unit automatically, one job per unit, in parallel. Only apply is dependency-ordered; a plan reads no other unit's output, so nothing has to wait.
  2. Read the plan comment. Each unit posts a fresh comment led by a validation check, then the plan output, a change summary, apply instructions, and the gate findings.
  3. Get the required reviews. /apply respects the repo's branch protection.
  4. Apply. Comment /apply to apply everything in the PR, or /apply -p <label> for one unit. Units apply in dependency order, one at a time.
  5. Merge. Once applied, the tf-pr-ops / merge-gate check turns green and the PR can merge.

Commands

Comment Effect
/plan Re-plan all impacted units.
/plan -p <label> Plan one unit. <label> is the unit path, its name, or its directory.
/apply Apply all impacted units, in dependency order.
/apply -p <label> Apply one unit.
/unlock Force-release the unit locks this PR holds.
digger plan / digger apply / digger unlock Accepted aliases, for muscle memory.

Opening, updating, or reopening a PR runs /plan automatically. A plan with changes includes an instructions block telling you exactly which /apply to run.

The impacted set

"Impacted" is wider than "has a plan diff", and wider than "the files you edited". A unit is impacted when any of three things is true:

  • its own directory changed,
  • one of the files it reads changed, or
  • it is downstream of an impacted unit through a dependencies block.

The read-files rule is the surprising one. Terragrunt reports every file a unit pulls in, which includes root.hcl, tenant.hcl, and the subscription.hcl and region.hcl above it. A one-line change to live/root.hcl therefore impacts every unit in the tree, because every unit reads it. Expect the full fan-out, and expect most of those plans to come back as no-ops.

That is the intent. A shared contract change is planned everywhere it lands, not only where someone remembered to look.

Cross-PR unit locks

The first pull request to plan a unit owns it until that pull request merges or closes. A second PR touching the same unit does not plan it: the unit reports a locked result instead, with a comment naming the owning PR, a failing per-unit check, and a failing merge gate reading Unit locked by another PR. This stops a stale plan in one PR from silently reverting what another PR applied.

Locks release three ways:

  • Merging or closing the owning PR. This is the normal path; the caller workflow's closed trigger is what makes it fire.
  • Commenting /unlock on the owning PR force-releases its locks, for when a PR is parked but should not block others. The engine confirms with a comment.
  • Re-running the blocked PR's plan after the lock is gone acquires it.

Ownership lives in an Azure Storage Table (tfprlocks) on the state storage account, one row per unit; the bootstrap grants the identities access. Locking skips itself on drift sweeps and manual dispatches, which never own units. A repository can point locks at a different account with the TFPR_LOCK_STORAGE_ACCOUNT variable, or turn the feature off with TF_PR_OPS_DISABLE_LOCKS set to true. See Secrets and variables.

The plan comment

Each run posts a new comment, so the PR keeps a chronological history of runs. A comment is ordered for scanning:

  • A validation check first, expanded: the pass or fail headline, so a plan error shows at the top instead of below a wall of output.
  • Pre-plan checks, if any pre-plan hooks produced output.
  • Plan output in a collapsible block.
  • A plan summary table of what will change.
  • Instructions with the exact /apply commands, expanded, when the plan has changes.
  • Additional output from the post_plan gates: policy, security, and cost. This panel is status-aware: collapsed with a check mark when every gate is clean, and auto-expanded with a warning when a gate reports a finding or the run failed, so a policy, security, or cost finding is never hidden.

Comment reactions

The engine reacts to your /plan or /apply comment so you can see progress at a glance without opening the run:

  • 👀 the command was seen and is being resolved,
  • 🚀 the plan or apply is running,
  • 🎉 it succeeded, or 👎 it failed (the 🚀 is removed when it finishes).

The running and final states also appear as a native check run (a spinner, then a green check or red cross) in the PR's Checks section.

The command gate

Before any of that, the engine checks who typed the command. /plan and /apply are accepted only from a comment whose author_association is OWNER, MEMBER, or COLLABORATOR. Anyone else gets a 😕 reaction and a reply saying the command was refused. This is not a write-permission check: MEMBER means organisation member, which can include people with read-only access. See Who can run a command.

The approval gate

/apply is blocked until the PR meets the repo's required reviews. When it is not yet approved, the engine posts a comment explaining that apply is blocked, and the apply job does not run. This uses GitHub's own review decision, so it is configured entirely in branch protection or rulesets.

A repository that requires no approving reviews at all is a special case: GitHub reports no review decision, and the engine refuses that too. Such a repository opts back in with the TF_PR_OPS_ALLOW_UNREVIEWED_APPLY variable. See Secrets and variables.

Dependency-ordered apply

Apply is a single job, not a parallel matrix, so Terragrunt units apply after the units they depend on. If a dependency fails, its dependents are skipped rather than applied against stale state, and the skipped units are reported.

The merge gate

The tf-pr-ops / merge-gate status check is the safety net. It is green when:

  • the PR has no Terraform changes, or
  • every unit is a no-op, or
  • every impacted unit has been applied.

It is red while changes remain unapplied, or when a plan or apply failed. It also blocks when the PR changed Terraform but discovery selected no units, so an unapplied net-new unit or a removed unit cannot slip through green. Made a required check, it guarantees the main branch always matches applied infrastructure.