Skip to content

Command reference

Commands are comments on a pull request.

Comment Effect
/plan Plan all changed units (re-plan).
/plan -p <label> Plan one unit.
/apply Apply all changed units in the PR, in dependency order.
/apply -p <label> Apply one unit.
/unlock Force-release the unit locks this PR holds.
digger plan Alias for /plan.
digger apply Alias for /apply.
digger unlock Alias for /unlock.

Selecting a unit

<label> after -p (or --project, or as a bare argument) matches a unit by:

  • its full-path label (for example live/landingzones/corp/corp-workload/westeurope/demo),
  • its name (the leaf folder), or
  • its directory.

A plan with changes tells you the exact label to use in its instructions block. The flag is spelled --project because that is the engine's word for a unit; the two mean the same thing here.

Who can run a command

/plan and /apply are accepted only from a comment whose GitHub author_association is OWNER, MEMBER, or COLLABORATOR. Any other author, for example CONTRIBUTOR or NONE, is refused: the engine adds a 😕 reaction to the comment and posts a reply naming the author association it saw.

This is the first of the three gates on the flow. It answers "who is asking". The approval gate below reads a property of the pull request, not of the person typing, so without this check any user who can comment on the PR could drive a real apply.

This is not a write-permission check

author_association is what GitHub reports on the comment, not the author's permission level on the repository. MEMBER means "a member of the organisation that owns the repository", which in an organisation with default read access can include people with no write permission. If your organisation grants broad membership, do not treat this gate as an apply permission on its own. The required-review gate below is what actually decides whether an apply may run.

The check only exists on the comment path. Runs from pull_request events, workflow_dispatch, and workflow_call never see an author_association, so they are not filtered by it.

Automatic runs

  • Opening, updating, or reopening a PR runs /plan on the changed units.
  • Closing or merging a PR releases the unit locks it holds. See Cross-PR unit locks.
  • The workflow also declares a manual workflow_dispatch with a command (plan or apply) and an optional project input.

Manual dispatch needs an open pull request

A dispatch run with no pull request to resolve against fails in the resolve job. This is a known limitation of the current engine. Drive plan and apply from PR comments, and use manual dispatch only where a pull request exists. The drift workflow's own dispatch is not affected, because a drift sweep never needs a PR.

What each run posts

  • A fresh run-report comment (so the PR keeps a history of runs).
  • A per-unit check in the PR checks (a spinner while running, then a green check or red cross).
  • An update to the tf-pr-ops / merge-gate status.
  • An update to the tf-pr-ops / approval status.
  • Reactions on the command comment that track its lifecycle: 👀 seen, 🚀 running, 🎉 succeeded, 👎 failed.

The approval status

Every run sets a tf-pr-ops / approval commit status from GitHub's review decision, so a required-approval rule is visible from the first plan rather than only when an apply is refused. It reads:

Review decision State Description
APPROVED success Required reviews met
empty, with TF_PR_OPS_ALLOW_UNREVIEWED_APPLY set to true success No reviews required; unreviewed apply allowed
empty, otherwise failure No reviews required by this repository; apply is blocked
anything else failure Required reviews not met (<decision>)

An empty review decision means the repository requires no reviews at all. The engine refuses that rather than treating it as consent: an empty decision cannot be told apart from a ruleset that was removed, so allowing it would let the apply gate disappear with no signal. A repository that requires no reviews on purpose opts back in with the TF_PR_OPS_ALLOW_UNREVIEWED_APPLY repository variable set to true. Leave it unset everywhere else. See Secrets and variables.

Never make tf-pr-ops / approval a required check

It is informational. It blocks nothing on its own, and it reports failure on every PR until someone approves. Make it required and you deadlock the repository: the status only clears on approval, but the branch rule already enforces approval, so the check adds nothing and can wedge merges. The gate you do make required is tf-pr-ops / merge-gate.

/apply is gated separately, on the same review decision. If the PR does not meet the required reviews, the engine posts a comment saying apply is blocked and does not run.

Template repositories are skipped

Both workflows exit before resolve when the caller repository is a GitHub template repository. A template holds no infrastructure and none of the bootstrap-created variables or secrets, so plan and apply cannot work there, and skipping keeps its own pull requests green. A repository generated from a template is not itself a template, so a customer repo never hits this.