Terraform PR reviews
with a decision you can check

Paste terraform show -json. Get ALLOW, REVIEW or BLOCK from a deterministic gate, plus a comment for the PR. The model explains. It cannot change the decision.

Try the three cases below ↓

Three real gate outcomes

IAM narrowed to Allow. A database destroy that must Block. A policy the plan does not show, so Review. Or paste your own terraform show -json.

plan.json
🔍

Results appear here

Pick an example and click Analyze to see PlainPlan in action

Terraform JSON can carry plaintext secrets. The demo sends the plan to PlainPlan. The gate is computed from the plan and does not use the model; the summary does.

Three steps in the PR you already have
1

Plan

Export the plan JSON you already generate in CI.

terraform show -json tfplan > plan.json
→
2

Gate

The Action posts one comment and returns ALLOW, REVIEW or BLOCK. Public repos need no API key.

uses: adamnmcc/plainplan-action@v1
→
3

Review

Read the findings on the resources that changed. Set fail_on_risk if you want the merge blocked.

fail_on_risk: HIGH
A decision, then the explanation
⚖

Deterministic gate

ALLOW, REVIEW or BLOCK from versioned rules. The LLM cannot set severity or talk the gate into ALLOW.

ALLOW — permission reduced, no new exposure
REVIEW — policy unknown until apply
BLOCK — stateful database destroy
⚠️

Resource findings

Each finding names a resource and a rule. Coverage gaps stay visible instead of becoming silent ALLOW.

critical aws_db_instance.prod — stateful delete
high sg rule — public ingress expanded
medium iam policy — value unknown
☑

Reviewer checklist

Questions tied to the changes in this plan, not a generic security lecture.

☐ Confirm the database can be destroyed
☐ Check the widened CIDR is intentional
☐ Re-plan if a security value is unknown
📄

PR comment + receipt

Markdown on the pull request, plus a receipt that hashes the exact plan that was assessed. Unsigned today; not a proof of apply.

decision: BLOCK   policy: 2026-09-23.2
plan_hash: sha256:4f2c…
signed: false
Drop it into any CI pipeline
⚡ uses: adamnmcc/plainplan-action@v1
  • Public repos: omit the API key. Add id-token: write so the Action can use GitHub OIDC.
  • Private repos: create a key below and store it as PLAINPLAN_API_KEY.
  • Optional: fail_on_risk plus statuses: write publishes a plainplan commit status you can require on the branch.
# .github/workflows/terraform-plan-review.yml on: pull_request: paths: ['**.tf'] jobs: plan-review: runs-on: ubuntu-latest permissions: contents: read pull-requests: write id-token: write statuses: write steps: - uses: actions/checkout@v4 - uses: hashicorp/setup-terraform@v3 - run: terraform init && terraform plan -out=tfplan - run: terraform show -json tfplan > plan.json - name: Analyze with PlainPlan uses: adamnmcc/plainplan-action@v1 with: plan_file: plan.json fail_on_risk: HIGH
  • Install (Linux, macOS, WSL) — needs curl + jq
  • Run it in any Terraform dir — no key needed to try (demo mode)
  • Set PLAINPLAN_API_KEY for the full AI review
# install curl -fsSL https://raw.githubusercontent.com/adamnmcc/plainplan-cli/v1/install.sh | sh # run terraform plan + review it, fail on HIGH risk plainplan run --fail-on-risk HIGH # or feed a plan you already have terraform show -json tfplan | plainplan
# Generate a key at https://dev.plainplan.click, then: terraform show -json tfplan > plan.json RESULT=$(curl -s -X POST 'https://dev.api.plainplan.click/api/analyze?include=pr_markdown' \ -H "Authorization: Bearer $PP_KEY" \ -H "Content-Type: application/json" \ -d @plan.json) # Gate fields are always present — the model cannot change them echo "$RESULT" | jq -r '.analysis.decision, .analysis.risk_score, .analysis.receipt.plan_hash' MARKDOWN=$(echo "$RESULT" | jq -r '.analysis.pr_markdown') gh pr comment $PR_NUMBER --body "$MARKDOWN"

Review the plan that is actually going to apply

Works with Terraform and OpenTofu. Drops into GitHub Actions. The gate is deterministic; the summary is explanation. No platform lock-in.