Skip to content

Lock roots to a pull request

llms.txtlists every page for an agent
Optional: hand this page to your coding agentThe steps work by hand too.
Show the whole prompt
Read https://intentius.io/terragucci/guides/lock-roots/.
Add `locks: plan` to terragucci.yml, run `npx terragucci config check --json` and `npx terragucci init`, and open a pull request with the result.
Tell me the status check to require in branch protection; do not change branch protection yourself.
Never comment `/terragucci lock` or `/terragucci unlock`.
Never apply, approve (a pull request review or `terragucci approve`), override a policy denial (`terragucci override`), use `--mode apply`, or merge; never touch `.chant/allowed_signers` or `chant/lifecycle`.

Each root an open change reaches is held for it. A second change that reaches a held root is refused, and the reply names the root and its holder.

These holds work across pull requests. The backend’s state lock keeps two writers off one state file while a binary runs, a separate layer (Locking and staleness).

Setting Roots are held Forges
locks: apply (the default), with apply.when: pull-request when the change applies before merge, or a writer comments /terragucci lock GitHub, GitLab, Forgejo
locks: plan from the first plan, and again on each push or /terragucci plan GitHub, Forgejo

Under the defaults, apply.when: merge and locks: apply, nothing is held.

init and config check refuse locks: plan on GitLab. The hold is taken by a job that runs from the default branch, and no merge request event on GitLab runs such a job. With apply.when: pull-request on GitLab, the comments schedule reads /terragucci apply and /terragucci lock on a merge request and takes the hold.

  1. Set the mode in terragucci.yml:

    locks: plan
  2. Check the file and write the pipeline again:

    Terminal window
    npx terragucci config check
    npx terragucci init

    init adds the pr-lock job. It runs from the default branch on each event and comment, with no checkout of the change and no cloud role, and reads the change as data from git.

  3. Open a pull request with the file and the pipeline, and merge it.

  4. Require terragucci/lock in branch protection. Under apply.when: merge the hold gates nothing by itself; the required status keeps a refused change from merging.

  5. Open a change. terragucci/lock passes and names the roots it holds. A second change that reaches one gets a failing terragucci/lock and a reply naming the root and the holder.

Nothing queues a refused change. Once the holder lets go, comment /terragucci plan on it to take the roots.

Comment, by anyone with write access Does
/terragucci lock holds every root the change reaches and applies nothing; the reply names the roots
/terragucci unlock lets go of the change’s roots; the reply names them

On GitLab a Developer or above comments, and the comments schedule answers on its next run. Both need apply.when: pull-request or locks: plan.

Event Held roots
the holder merges or closes released
/terragucci unlock on the holder released
a push stops reaching a root, with locks: plan that root is released

The holds are kept in _locks/tf-apply.json on chant/lifecycle. A hold whose pull request merged or closed counts as released. A fork’s pull request takes none.

Outside plain roots, a change reaches more than the files it touches:

Repo Held
Terragrunt the units it touches and every unit whose dependency or dependencies block names one, followed through; a change outside every unit, such as root.hcl, holds every unit
Atmos the instances of each component it touches and their dependents; a stack manifest or atmos.yaml holds every instance
synth, such as CDK Terrain every stack, for any file but Markdown

A Markdown-only change holds nothing. Locks has the full rules.

terragucci

These docs count page views and clicks with PostHog. They set no cookies, store nothing in your browser, and send nothing when your browser asks not to be tracked.