---
title: "Review the plan and apply only from CI · Agent Wranglers"
url: https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/plan-in-review
updated: 2026-10-20
licence: "All rights reserved"
---

# Review the plan and apply only from CI

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Infrastructure as code](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code)
- Rule: Post the plan in every pull request. Apply only from CI after merge, from the plan file made for that commit. No person applies from a laptop.
- How: On each pull request, CI runs `plan` with a read-only role and posts the summary. After merge, one job runs `plan -out`, prints the plan and saves the file as a short-lived build artifact. The apply job waits for a person to approve that printed plan, for example through a protected environment. Then it applies that exact file. A saved plan refuses to apply when the state changed in between. Plan files can hold secrets, so encrypt them or keep them for 1 day at most. Write access belongs only to the CI role, as in [separate roles for plan and apply](https://agentwranglers.se/playbook/infrastructure/identity-and-access/separate-plan-and-apply-roles). Protect the branch as in [protect the main branch](https://agentwranglers.se/playbook/engineering/version-control-and-review/protect-the-main-branch).
- Check: The workflow file runs `apply` only on a saved plan file, in a job limited to the main branch and a protected environment. No human role has write rights on the state bucket.

Example:

```yaml
# GitHub Actions: plan on pull requests, apply the saved plan after merge
on:
  pull_request:
  push:
    branches: [main]
permissions:
  id-token: write
  contents: read
jobs:
  plan:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<commit-sha>
      - run: tofu init -lockfile=readonly && tofu plan -no-color
  plan-release:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<commit-sha>
      - run: tofu init -lockfile=readonly && tofu plan -no-color -out=release.tfplan
      - uses: actions/upload-artifact@<commit-sha>
        with: { name: release-plan, path: release.tfplan, retention-days: 1 }
  apply:
    needs: plan-release
    environment: production   # a person approves the plan printed above
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@<commit-sha>
      - uses: actions/download-artifact@<commit-sha>
        with: { name: release-plan }
      - run: tofu init -lockfile=readonly && tofu apply release.tfplan
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers infrastructure playbook: https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/plan-in-review`.

- Previous: [Keep state remote with locking and encryption](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/remote-state)
- Next: [Pin the tool and provider versions](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code/pinned-provider-versions)
- [Infrastructure as code](https://agentwranglers.se/playbook/infrastructure/infrastructure-as-code)
