# Share a Terraform Plan — Readable, Reviewable, Not Truncated

Canonical: https://commareports.com/share-terraform-plan
Published: 2026-08-21

> Terraform plan output dies in PR comments: truncated at the character limit, unreadable in monospace, impossible to comment on line by line. Publish an HTML plan summary to Comma — one URL per stack, anchored comments on the destroys, a revision per run.

# Share a Terraform plan

A plan is the single most consequential artifact in an infrastructure
change, and it is shared worse than anything else in the pipeline.

The default is a pull request comment. Which means:

- **It gets truncated.** GitHub caps a comment at 65,536 characters. A
  plan touching thirty resources clears that, so the bot collapses it
  behind `<details>` and cuts the middle out — often the part with the
  `destroy` in it.
- **It's unreadable.** Hundreds of lines of `~ tags = { ... }` noise
  wrapped in a monospace block, with the three lines that matter buried
  somewhere in the middle.
- **You can't point at anything.** "The RDS one, further down" is not a
  code review. Line-anchored comments work on the diff of your `.tf`
  files, not on the plan the diff produces.
- **It's gone next week.** The plan that was approved lives in a comment
  thread on a merged PR, if it lives anywhere.

And the people who most need to read it — a security reviewer, the DBA,
whoever owns the account you're about to modify — often can't run
`terraform plan` themselves. They have no state access and no
credentials, which is the correct security posture and a terrible
review workflow.

## The pipeline

Plan, serialize, render, publish. The middle step is a script you own —
30 lines over `resource_changes` is enough:

```bash
terraform plan -out=tfplan
terraform show -json tfplan > plan.json

# plan.json → plan.html  (group by action: create / update / replace / destroy)
python3 render_plan.py plan.json > plan.html

curl -fsS -X PATCH "https://commareports.com/api/v1/reports/$REPORT_ID" \
  -H "Authorization: Bearer $COMMA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --rawfile html plan.html \
        --arg title "prod-network — plan $(git rev-parse --short HEAD)" \
        '{title: $title, html: $html}')"
```

A plan report that reviews well leads with the counts (`4 to add, 2 to
change, 1 to destroy`), puts **destroys and replacements first**, and
keeps the per-resource attribute diffs collapsed underneath. Comma
renders report HTML verbatim with scripts enabled inside a sandboxed
iframe, so `<details>` blocks, a filter toggle, or a small
sort-by-action control all work — see [interactive HTML
reports](/interactive-html-reports).

Then the PR comment becomes one line with a link, which is what a PR
comment should have been all along.

## One URL per stack

Create the report once per stack or workspace, store its id as a CI
variable, and `PATCH` it on every run. Each run appends a revision at the
same URL:

- The link in the change ticket never goes stale.
- Two revisions can be [diffed](/docs/ci) — "this plan added a destroy
  that wasn't in the one we approved on Tuesday" is visible instead of
  remembered.
- Open comment threads carry across revisions, so an unresolved
  objection follows the plan into the next run rather than disappearing
  with it.

For a plan-per-PR workflow, create one report per PR instead and keep
the stack-level report for the mainline. Both patterns are one `curl`.

## Comments land on the destroy

This is what a PR comment thread can't do. A reviewer opens the plan,
highlights the row for
`aws_db_instance.analytics → destroy/create (engine_version)`, and pins a
thread to it: "this forces a replacement — we need the snapshot restore
runbook before this merges." The thread is attached to _that resource_,
survives the next plan, and is answerable in place. That's the [anchored
comment model](/comment-on-html).

Two things follow from that:

- **Approval has an artifact.** The plan that was reviewed, the thread
  where the objection was raised, and the reply that resolved it are one
  URL with a revision history — the thing an auditor asks for six months
  later.
- **An agent can close the loop.** A Claude Code agent attached through
  [Comma's MCP server](/mcp) can read the threads with `list_comments`,
  adjust the module, re-plan, and `update_report` — same URL, new
  revision, reply on the thread.

## Handle it as sensitive, because it is

Terraform redacts attributes it knows are sensitive in `show -json`
output, but that is not the whole risk surface. Resource names, CIDR
blocks, account ids, ARNs, instance types and tag values are a decent map
of your infrastructure.

- Keep plan reports at **private** or **team** visibility. Never public.
  See the [sharing model](/docs/sharing).
- Mint the CI token with **`reports:write` only** and nothing else —
  scopes and instant revocation are covered in [API
  tokens](/docs/api-tokens).
- Filter in your renderer. If a module emits values you don't want
  rendered at all, drop them in the script rather than relying on
  Terraform's `sensitive` marking to cover you.

## Try it

Comma is free — unlimited reports, unlimited commenters, unlimited
revision history. Take the next plan your bot was going to truncate,
publish it, and let the PR comment be a link.

**[Create your first report →](https://commareports.com/)**

### Related

- [Publish from CI](/docs/ci) — the one-curl pipeline pattern
- [Share an interactive HTML report](/interactive-html-reports) — what runs inside the sandbox
- [Scoped tokens for AI agents](/agents/scoped-tokens-for-ai-agents) — least-privilege publishing
- [Comment on an HTML report](/comment-on-html) — how anchored threads work
