John

Senior Cloud Engineer & Technical Lead

Building a Chrome Extension to Tame Atlantis PR Comment Chaos

If you’ve ever had to scroll through forty-plus Atlantis comments on a single GitHub PR just to find the one workspace you need to apply, you know the specific kind of frustration I’m talking about. It’s the kind of friction that doesn’t seem like a big deal until you’re doing it fifteen times a day across multiple PRs, losing context every time you scroll past walls of plan output looking for atlantis apply -w production.

I finally got tired of it and built a Chrome extension to fix the problem.

What Atlantis Does (and Why It Gets Noisy)

For anyone unfamiliar, Atlantis is a self-hosted Go application that listens for Terraform pull request events via webhooks. When you open a PR that modifies Terraform code, Atlantis automatically runs terraform plan and comments back on the PR with the output. You manage your infrastructure by posting commands like atlantis plan -w staging or atlantis apply -w production directly in PR comments. It’s a CNCF Sandbox project with over 8,800 stars, and for good reason – it makes Terraform collaboration through pull requests seamless.

The problem isn’t Atlantis itself. Atlantis is excellent at what it does. The problem is what happens when your Terraform repository has many workspaces.

The Problem: Death by a Thousand Comments

In a typical multi-environment setup, a single Terraform repo might have workspaces for dev, staging, production, shared-services, monitoring, and maybe regional variants like us-east-1-prod and eu-west-1-prod. Some repos have ten, fifteen, even twenty workspaces. Every workspace gets its own plan comment. Every apply gets its own comment. Every re-plan after a code change generates another round.

A single PR touching shared modules can easily produce 30+ Atlantis comments. Here’s what a typical comment thread starts to look like:

## Atlantis Plan - workspace: dev

<details><summary>Show Output</summary>

Terraform will perform the following actions:

  # aws_security_group_rule.ingress will be created
  + resource "aws_security_group_rule" "ingress" {
      + cidr_blocks       = ["10.0.0.0/8"]
      + from_port         = 443
      + protocol          = "tcp"
      + security_group_id = "sg-0a1b2c3d4e5f"
      + to_port           = 443
      + type              = "ingress"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

</details>

* :arrow_forward: To **apply** this plan, comment:
  * `atlantis apply -w dev`
* :put_litter_in_its_place: To **delete** this plan and lock, comment:
  * `atlantis unlock`

---

Now imagine that block repeated for every workspace. Multiply it by plan revisions when you push new commits. The comment thread becomes a wall of collapsed <details> blocks, each one looking nearly identical. Finding the specific workspace you care about means either scrolling through everything or using your browser’s Ctrl+F and hoping you get the right match on the first try.

The daily workflow becomes:

  1. Open the PR
  2. Scroll… scroll… scroll…
  3. Find the workspace comment you need
  4. Expand the plan output
  5. Review the changes
  6. Scroll back up to find the apply command
  7. Copy-paste atlantis apply -w the-workspace-name
  8. Repeat for the next workspace

It’s death by a thousand paper cuts.

The Solution: A Workspace Navigation Panel

I built a Chrome extension that adds a hover panel to GitHub PR pages. The panel sits on the right edge of the screen and overlays the page content when you hover over it. It scans the PR comment thread for Atlantis comments, extracts the workspace names, and creates shortcut buttons for each one.

Here’s what it gives you:

  • Instant workspace index: A compact list of every Atlantis workspace detected in the PR
  • One-click navigation: Click a workspace name and the page scrolls directly to that comment
  • Quick action buttons: Trigger atlantis plan or atlantis apply for a specific workspace without hunting for the command
  • Status at a glance: See which workspaces have been planned, which are waiting for apply, and which have already been applied
flowchart LR subgraph Before["Before: Manual Navigation"] direction TB B1[Open PR] --> B2[Scroll through 30+ comments] B2 --> B3[Find workspace comment] B3 --> B4[Expand plan output] B4 --> B5[Scroll to find apply command] B5 --> B6[Copy-paste command] B6 --> B2 end subgraph After["After: Extension Panel"] direction TB A1[Open PR] --> A2[Glance at workspace panel] A2 --> A3[Click workspace name] A3 --> A4[Jumped directly to comment] A4 --> A5[Click apply button] end style Before fill:#4a1a1a,stroke:#8b3a3a,color:#fff style After fill:#1a4a1a,stroke:#3a8b3a,color:#fff

What used to take 30 seconds of scrolling and squinting now takes a single click.

How It Works Under the Hood

The extension is straightforward in concept but required some careful DOM parsing to get right. Here’s the architecture:

flowchart TD A[Extension Loads on PR Page] --> B[Content Script Injected] B --> C[Scan PR Comment Thread] C --> D{Atlantis Comments Found?} D -->|Yes| E[Parse Workspace Names] D -->|No| F[Exit - No Panel Shown] E --> G[Extract Plan/Apply Status] G --> H[Build Navigation Panel] H --> I[Attach to Page Right Edge] I --> J[Listen for New Comments] J --> C style A fill:#2d3748,stroke:#4a5568,color:#fff style H fill:#2a4365,stroke:#3182ce,color:#fff style J fill:#1a365d,stroke:#2c5282,color:#fff

Detecting Atlantis Comments

Atlantis comments follow a consistent pattern. The comment header always contains the workspace name, which makes parsing reliable. The content script walks through every comment on the PR page and matches against known Atlantis patterns:

function findAtlantisComments() {
  const comments = document.querySelectorAll('.timeline-comment');
  const workspaces = [];

  comments.forEach((comment, index) => {
    const body = comment.querySelector('.comment-body');
    if (!body) return;

    // Atlantis plan comments contain a predictable header pattern
    const headerMatch = body.textContent.match(
      /Atlantis\s+(Plan|Apply)\s+-\s+(?:dir:\s+\S+\s+)?workspace:\s+(\S+)/i
    );

    if (headerMatch) {
      workspaces.push({
        type: headerMatch[1].toLowerCase(),   // 'plan' or 'apply'
        workspace: headerMatch[2],             // e.g., 'production'
        element: comment,                      // DOM reference for scrolling
        commentIndex: index
      });
    }
  });

  return workspaces;
}

The regex accounts for Atlantis’s standard comment format. Whether the comment says Atlantis Plan - workspace: staging or Atlantis Plan - dir: infra/vpc workspace: production, the pattern picks it up.

Building the Navigation Panel

Once workspaces are detected, the extension builds a compact panel that groups them logically:

function buildPanel(workspaces) {
  const panel = document.createElement('div');
  panel.id = 'atlantis-nav-panel';

  // Group workspaces by name, tracking latest plan/apply status
  const grouped = {};
  workspaces.forEach(ws => {
    if (!grouped[ws.workspace]) {
      grouped[ws.workspace] = { plan: null, apply: null };
    }
    // Keep reference to the most recent comment of each type
    grouped[ws.workspace][ws.type] = ws;
  });

  Object.entries(grouped).forEach(([name, status]) => {
    const row = document.createElement('div');
    row.className = 'workspace-row';

    // Workspace name as a clickable link
    const label = document.createElement('span');
    label.className = 'workspace-label';
    label.textContent = name;
    label.addEventListener('click', () => {
      const target = status.plan?.element || status.apply?.element;
      target?.scrollIntoView({ behavior: 'smooth', block: 'start' });
    });

    // Quick action buttons
    const planBtn = createActionButton('Plan', name);
    const applyBtn = createActionButton('Apply', name);

    row.append(label, planBtn, applyBtn);
    panel.appendChild(row);
  });

  document.body.appendChild(panel);
}

Triggering Commands

The quick action buttons work by programmatically inserting the Atlantis command into the PR comment box and submitting it. This saves you from manually typing atlantis apply -w my-long-workspace-name every time:

function createActionButton(action, workspace) {
  const btn = document.createElement('button');
  btn.className = `atlantis-btn atlantis-${action.toLowerCase()}`;
  btn.textContent = action;

  btn.addEventListener('click', (e) => {
    e.stopPropagation();
    const command = `atlantis ${action.toLowerCase()} -w ${workspace}`;

    // Find the PR comment textarea
    const commentBox = document.querySelector('#new_comment_field');
    if (commentBox) {
      commentBox.value = command;
      commentBox.dispatchEvent(new Event('input', { bubbles: true }));
      commentBox.focus();
      // Scroll to the comment box so the user can review and submit
      commentBox.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
  });

  return btn;
}

I intentionally chose not to auto-submit the comment. The extension fills in the command and scrolls to the comment box, but you still hit the submit button yourself. For something that triggers terraform apply on production infrastructure, I wanted that deliberate final click.

Styling the Panel

The panel itself is minimal – it hugs the right edge of the page and expands on hover so it doesn’t interfere with normal PR reading:

#atlantis-nav-panel {
  position: fixed;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 40px;                          /* Collapsed: just a thin strip */
  max-height: 80vh;
  overflow-y: auto;
  background: #1c2028;
  border-left: 2px solid #3b82f6;
  border-radius: 8px 0 0 8px;
  padding: 8px 4px;
  z-index: 1000;
  transition: width 0.2s ease;
  opacity: 0.7;
}

#atlantis-nav-panel:hover {
  width: 280px;                         /* Expanded: shows full workspace names */
  padding: 12px;
  opacity: 1;
}

.workspace-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 0;
  border-bottom: 1px solid #2d3748;
}

.workspace-label {
  flex: 1;
  color: #e2e8f0;
  cursor: pointer;
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.workspace-label:hover {
  color: #3b82f6;
  text-decoration: underline;
}

.atlantis-btn {
  font-size: 11px;
  padding: 2px 6px;
  border: none;
  border-radius: 3px;
  cursor: pointer;
}

.atlantis-plan {
  background: #2563eb;
  color: white;
}

.atlantis-apply {
  background: #16a34a;
  color: white;
}

Handling Dynamic Content

GitHub loads PR comments dynamically, especially on long threads. The extension uses a MutationObserver to watch for new comments being added to the page and re-scans when the DOM changes:

const observer = new MutationObserver((mutations) => {
  const hasNewComments = mutations.some(m =>
    Array.from(m.addedNodes).some(node =>
      node.nodeType === 1 && node.querySelector?.('.timeline-comment')
    )
  );

  if (hasNewComments) {
    refreshPanel();
  }
});

observer.observe(
  document.querySelector('.js-discussion'),
  { childList: true, subtree: true }
);

This means when Atlantis posts a new plan result or you trigger an apply, the panel updates automatically without a page refresh.

The Manifest

The Chrome extension manifest is straightforward. It only activates on GitHub PR pages:

{
  "manifest_version": 3,
  "name": "Atlantis Workspace Navigator",
  "version": "1.0.0",
  "description": "Quick navigation for Atlantis Terraform workspaces in GitHub PRs",
  "permissions": ["activeTab"],
  "content_scripts": [
    {
      "matches": ["https://github.com/*/*/pull/*"],
      "js": ["content.js"],
      "css": ["panel.css"],
      "run_at": "document_idle"
    }
  ]
}

A few things worth noting:

  • Manifest V3: Using the latest Chrome extension format for future compatibility
  • Minimal permissions: Only activeTab is needed since the extension just reads and modifies the current page DOM
  • URL matching: The pattern https://github.com/*/*/pull/* ensures the extension only loads on GitHub PR pages, not issues, repos, or other GitHub pages
  • document_idle: Waits until the page is fully loaded before injecting, which avoids race conditions with GitHub’s own JavaScript

The Mental Model

The way I think about this extension is as an index for a book. Atlantis comments are the pages of content, and the extension is the table of contents that tells you exactly where to find what you need.

flowchart TD subgraph PR["GitHub PR Comment Thread"] C1["Comment #1: Plan - dev
35 lines of output"] C2["Comment #2: Plan - staging
42 lines of output"] C3["Comment #3: Plan - production
28 lines of output"] C4["Comment #4: Apply - dev
Success output"] C5["Comment #5: Plan - us-east-1-prod
51 lines of output"] C6["Comment #6: Plan - eu-west-1-prod
47 lines of output"] end subgraph Panel["Extension Panel"] W1["dev - Plan / Apply"] W2["staging - Plan / Apply"] W3["production - Plan / Apply"] W4["us-east-1-prod - Plan / Apply"] W5["eu-west-1-prod - Plan / Apply"] end W1 -.->|click| C1 W1 -.->|click| C4 W2 -.->|click| C2 W3 -.->|click| C3 W4 -.->|click| C5 W5 -.->|click| C6 style PR fill:#1a1a2e,stroke:#4a5568,color:#fff style Panel fill:#1a365d,stroke:#3182ce,color:#fff

Without the index, you’re flipping through every page hoping to land on the right chapter. With it, you go straight to what you need.

What I’d Add Next

This started as a quick productivity hack, but there are a few features I’m considering:

  • Plan diff summary: Show a compact +3 -1 ~2 (added, destroyed, changed) count next to each workspace so you can see the blast radius at a glance
  • Workspace filtering: For repos with twenty-plus workspaces, a quick search filter within the panel itself
  • Status indicators: Color-coded icons showing whether a workspace plan is stale (new commits since last plan), waiting for apply, or already applied
  • Keyboard shortcuts: Navigate between workspaces with j/k without touching the mouse

Key Learnings

  • Small friction compounds into real productivity loss – Scrolling through Atlantis comments doesn’t seem like a big deal, but across dozens of PRs per week, it adds up to hours of wasted time
  • Chrome extensions are underrated for workflow optimization – A content script with DOM parsing and a bit of CSS can dramatically improve a tool you use every day without modifying the tool itself
  • Atlantis comment formatting is consistent enough to parse reliably – The workspace: pattern in headers makes it straightforward to extract workspace names programmatically
  • Deliberate friction is good for dangerous operations – Auto-filling the command but requiring manual submit for terraform apply is the right tradeoff between speed and safety
  • MutationObserver is essential for modern web apps – GitHub loads content dynamically, so static DOM parsing at page load isn’t enough; you need to watch for changes
  • Minimal permissions build trust – Using only activeTab instead of broad permissions makes the extension easier to justify installing on a work machine
  • Solve your own pain points first – The best developer tools come from scratching your own itch; if something frustrates you daily, there’s probably a simple fix waiting to be built

The extension took an afternoon to build and has already saved me more time than that investment. If you’re working with Atlantis and multi-workspace Terraform repos, the comment noise is a solved problem – you just need an index.