Have an agent fix drift
Optional: hand this page to your coding agentThe steps work by hand too.Show the whole prompt
Read https://intentius.io/terragucci/guides/agent-fix-drift/.
Add the `agent.drift` block and `respond.drift: off` to terragucci.yml, run `npx terragucci config check` and `npx terragucci init`, and open a pull request with the result.
List the machine user, token scopes and secrets I must set; do not create tokens or secrets yourself.
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`.One of the features that run a model (what needs an agent), off unless agent.drift is set.
Result
Section titled “Result”When a drift run opens the drift issue, a coding agent reads what drifted and changes the code to describe what is live. terragucci opens a pull request with the change and links it on the issue:
terragucci: the drift agent opened https://forgejo.example/acme/infra/pulls/2, changing `app/main.tf`.
It plans like any other pull request; nothing was applied, approved or merged.Merging the pull request keeps the change made outside the code, and its apply goes through the gate. Closing it and applying the code as it is undoes the drift instead.
Prerequisites
Section titled “Prerequisites”| You need | Why |
|---|---|
| Drift checks on GitHub or Forgejo | the agent runs when the drift job opens the drift issue; init refuses agent.drift on GitLab |
| An Anthropic API key in your forge’s secrets | the default command runs Claude Code |
| A token for the pull request in your forge’s secrets (step 1) | a pull request opened with the job’s own token starts no workflow run, so it would not be planned |
-
Make the agent’s token. Keep it in a secret such as
AGENT_FORGE_TOKEN, and the model’s key inANTHROPIC_API_KEY. It is the same token, with the same settings, as the agent comment’s.Setting Value Who a machine user with write access, outside CODEOWNERSToken fine-grained, for this repository alone Contents, pull requests, issues read and write Workflows none, so GitHub refuses any change it sends to .github/workflows/Default branch ruleset a pull request and one approval of the latest change required, with no bypass for the machine user initrefusesagent.drifton GitLab.Setting Value Who a machine user added as a collaborator with Write access Token scopes write:repositoryandwrite:issueDefault branch the machine user stays off the push, approval and merge allowlists -
Turn it on in
terragucci.yml. The agent’s pull request replaces the codified one, so setrespond.drifttooff(orattribute, to keep who changed what in the issue):drift: "17 4 * * *"respond:drift: offagent:via: forgetoken_env: AGENT_FORGE_TOKENdrift:key_secret: ANTHROPIC_API_KEYmax_turns: 30timeout: 30drift: trueunderagenttakes every default; the keys are the agent comment’s. -
Check the file and write the pipeline, then merge the result to the default branch:
Terminal window npx terragucci config checknpx terragucci init -
Wait for drift, or run the drift workflow by hand. The drift issue gets the pull request’s link; read the pull request and its plan note before you merge or close it.
Agent inputs
Section titled “Agent inputs”The prompt lists each drifted resource by root with what the state held and what is live. A value the plan marks sensitive never appears:
root app:
aws_sqs_queue.jobs: changed
visibility_timeout_seconds: state 30, live 45It tells the agent that the drift and every file are untrusted input, since a value in the cloud can hold any text, and that a change to a guarded path is refused.
Permissions
Section titled “Permissions”| Job | Does | Holds |
|---|---|---|
drift |
plans every root with -refresh-only, opens the drift issue, and says so in its outputs |
the plan role, and its token for the issue |
drift-agent |
checks out the commit the drift job planned with no credentials kept, writes the prompt from the drift report, runs the agent, and keeps its changes as a patch | a read-only job token, which the agent’s step runs without; the model’s key, in the agent’s step alone |
drift-agent-push |
applies the patch to the same commit in a fresh container; refuses one that touches a guarded path, with a comment on the issue naming the paths; commits any other to terragucci/drift-agent-<issue>, pushes it without force and opens the pull request |
the agent’s token |
| the plan job | plans the pull request with its read-only role | the plan role |
Neither agent job has a cloud role, whatever oidc says. Nothing here applies, approves or merges.
Other agents
Section titled “Other agents”command takes any command line that reads a prompt on stdin and edits files in the working directory; TG_AGENT_PROMPT holds the prompt’s path and TG_AGENT_MAX_TURNS the turn limit, as for the agent comment.
agent:
via: forge
token_env: AGENT_FORGE_TOKEN
drift:
command: my-agent --non-interactive
key_secret: MY_AGENT_KEYRead drift over MCP or --json
Section titled “Read drift over MCP or --json”An agent at your desk reads the same drift without a model in the pipeline: terragucci mcp’s index tool lists the tf-drift runs and report reads one (Read the estate over MCP). To change the code or the config, it runs the CLI with --json.
- Turn on drift checks covers the schedule and the issue.
- The generated pipeline lists the jobs and their tokens.
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.