PR gating

GitHub App setup, required vs advisory checks, affected scenario selection, caching, and GitHub Action integration.

PR gating

PR gating is Guard's pre-merge quality gate. On every pull request, Guard selects the scenarios affected by the diff, runs them against isolated Clones, and reports results as a GitHub Check Run — the primary surface developers see in the PR header.

Unlike running your full Playwright suite on every PR, Guard's affected-scenario detection computes which user journeys touch changed files, routes, and APIs — typically in a few seconds on large repos.

Product page: guard.molar.it · Dashboard: app.molar.it/dashboard/guard


How PR gating works

pull_request webhook (opened | synchronize | reopened)
Verify HMAC signature, dedupe delivery ID, enqueue guard-pr job
Create Check Run: "Detecting affected scenarios…"
Worker: compute affected set → spawn Clones → run Playwright
Stream logs (SSE) → update Check Run incrementally
All green → conclusion success
Any failure → conclusion failure + Mender preview in sticky comment

Two integration paths:

PathBest for
GitHub AppFull lifecycle: webhooks, affected selection, sticky comments, Mender previews
GitHub ActionHosted PR check for a registered scenario UUID (see Quick start)

Most production teams use the GitHub App for webhooks and the Action optionally for preview URL injection.


GitHub App permissions

Install the Molar Guard GitHub App from guard.molar.it or Onboarding in app.molar.it/dashboard/guard.

Required permissions

PermissionAccessWhy
checkswriteCreate and update Check Runs (primary PR surface)
pull_requestswriteSticky PR comments, request reviewers on Mender PRs
contentsread (+ write for Mender)Affected detection; Mender fix branches
metadatareadRequired for any GitHub App
statuseswriteFallback commit status when Check Run unavailable
actionsreadResolve last successful workflow on default branch for diff base
membersreadTag reviewers on Mender fix PRs

Webhook subscriptions

Guard listens for:

  • pull_request — opened, synchronize, reopened, closed, ready_for_review
  • push — refresh scenario graph on default branch
  • check_run.rerequested — re-run from GitHub UI
  • installation, installation_repositories — org onboarding
  • pull_request_review — Mender accept/reject signals

Webhooks are validated with HMAC-SHA256. Deliveries are deduplicated in Redis (24-hour replay window).


Install the GitHub App

  1. Go to guard.molar.it or Onboarding in the dashboard
  2. Click Connect GitHub and choose your organization
  3. Select repositories (all repos or specific subset)
  4. Complete the install — Guard receives an installation webhook
  5. Open a test PR to verify a Check Run appears

Make the check required

By default, new installations start in advisory mode (see below). When you are ready to block merges:

  1. In the dashboard, go to Onboarding or Settings → Repos
  2. Click Make molar/guard required — Guard guides branch protection setup
  3. In GitHub: Settings → Branches → Branch protection → add molar/guard as a required status check

Alternatively, set mode: required in your GitHub Action workflow.


Required vs advisory mode

ModeCheck Run on failureMerge blocked?Recommended
Advisoryneutral or informational failureNoFirst 30 days; building trust
RequiredfailureYesAfter advisory trial shows accurate gating

Advisory mode posts results and a banner: "Advisory mode — Molar would have blocked this." Engineers see what would have failed without blocking the team during onboarding.

Required mode enforces the Check Run as a branch protection status check. Failed scenarios block merge until fixed.

Configure per repo:

// molar-guard.config.ts
export default {
  required: true, // or false for advisory
};

Or via GitHub Action input:

- uses: molar/guard-action@v1
  with:
    api-key: $\{\{ secrets.MOLAR_API_KEY \}\}
    mode: required  # or advisory

When you're ready, the dashboard can help you flip from advisory to required after you've reviewed a few PR runs.


PR run lifecycle

Phase 1 — Queued

GitHub webhook arrives. Guard creates a Check Run with status: queued and title "Detecting affected scenarios…"

Phase 2 — In progress

Worker dequeues the guard-pr job (priority 10 in BullMQ):

  1. Compute affected scenario set (see Affected selection)
  2. Update Check Run → in_progress, "Running N scenarios"
  3. Spawn fresh Clone bundle per PR (seed = sha256(prNumber || headSha)[:8])
  4. Run Playwright with parallel workers (default 4; higher limits on paid tiers)
  5. Stream stdout and stderr to Redis Stream → SSE in the dashboard

Phase 3 — Complete

OutcomeCheck conclusionSticky comment
All scenarios passsuccess✓ summary table
Any failurefailureFailure cards + screenshots + Mender preview
All non-cached green, some cachedneutral or successShows ⊘ cached green rows

Check Run output example

## ✗ 2 of 12 scenarios failed

| Scenario | Status | Duration | Replay |
|----------|--------|----------|--------|
| Signup happy path | ✓ passed | 4.2s | [view]() |
| Stripe subscription upgrade | ✗ failed | 8.1s | [view]() |
| Password reset email | ⊘ cached green || [view]() |

### Failure: Stripe subscription upgrade
**Assertion:** expected `.subscription-status` text to equal `"Pro"`, got `"Free"`
**Step:** 7/12 (click "Upgrade to Pro")
**Screenshot:** ![](https://artifacts.molar.cloud/…/step-07.png)

🤖 **Mender suggests this fix:** [Preview patch]()

Check Run actions include Re-run failed, Re-run all, and View in Molar when the GitHub App integration is active.


Affected scenario selection

Guard selects only the scenarios affected by each PR's diff — using a scenario dependency graph and git change detection — so large repos don't run the full suite on every push.

Algorithm overview

Stage 1 — Static affected (always on):

  1. ScenarioGraph in Postgres — edges from scenarios to files, routes, APIs, env vars, and copy anchors. Refreshed on pushes to your default branch.
  2. Git diff between base and head SHA. Base prefers the last green SHA on your default branch (guard_last_green_sha), falling back to the PR's base commit when no green run exists yet.
  3. Parse diff with parse-git-diff for file + hunk ranges.
  4. For each changed file:
    • Direct file dependency lookup
    • Route handler pattern match (TypeScript/Express/Next/Nest, plus pattern-based Python/Go/Ruby extraction)
    • Transitive references via import graph (depth 3, cap 200 files)
  5. If .molar/scenarios/*.molar.md changed directly → always run that scenario.

Stage 2 — Predictive reranker (when history exists):

A heuristic reranker (LightGBM-weighted scoring) reorders priority so likely failures run first. Never drops a statically-affected scenario without a green run in the last 7 days.

Stage 3 — Fallback safety net:

ConditionAction
>50 scenarios affectedRun full suite; emit guard.affected_fallback metric
0 scenarios on non-trivial diff (>10 files or >500 LOC)Run full suite

Engineers see fallback events on the dashboard and tune the scenario graph.

Speed

Affected-set computation is designed to complete in a few seconds on large repos. Your first runs may be slower while the scenario graph warms up.

Full-suite insurance

Nightly cron and post-merge runs on your default branch execute the full suite. Affected-skip on PRs cannot let a regression land silently on main.

Debug affected selection

pnpm molar-guard affected --base <base-sha> --head <head-sha>

API: GET /v1/scenarios/:id/affected_by/:sha


Run caching

Guard skips scenarios already proven green on an ancestor commit.

Cache key (content + dependency tree + clone bundle + runner version):

sha256(scenario_content || dependency_tree_hash || clone_bundle_version || guard_runner_version)

If the cache key matches a previous green run on a SHA that is an ancestor of the current PR head, the scenario is marked cached_green and skipped. Check Run shows ⊘ cached green.

Disable caching per scenario

---
id: stripe-subscription-upgrade
cache: never  # always re-run — critical path
---

Clones on every PR run

Every PR run uses fresh Clone instances — no shared state between PRs or scenarios.

  • Deterministic seed: sha256(prNumber || scenarioId || cloneSurface)[:8] — same PR + scenario = same Stripe customer IDs
  • Destructive call detection: if a scenario hits api.stripe.com instead of the Clone, the SDK raises MolarSafetyError and the run fails safely

Clones are optional for plug-and-play mode (vanilla Playwright against preview URL). Enable in molar-guard.config.ts:

export default {
  clones: { enabled: true },
};

PR comment vs Check Run

SurfaceRole
Check RunPrimary — lives in PR header, blocks merge when required
Sticky commentSecondary — high-density table, screenshots, Mender buttons

The sticky comment is posted on first run and edited in place on subsequent runs using marker <!-- molar-guard:run-id=… --> — never spammed with new comments.

Comment includes:

  • Status table with per-scenario duration
  • Failure screenshot thumbnails in a collapsible <details> block
  • Link to the Molar dashboard run detail

GitHub Action integration

For a hosted check without the full GitHub App, pass your scenario UUID and optional preview URL:

- uses: molar/guard-action@v1
  with:
    api-key: $\{\{ secrets.MOLAR_API_KEY \}\}
    scenario-id: "00000000-0000-0000-0000-000000000001"
    base-url: $\{\{ steps.deploy.outputs.url \}\}
    mode: advisory

See the full workflow in Quick start.

The Action runs one registered scenario per workflow invocation. Use the GitHub App for affected selection across your catalog.


Parallelization and plan tiers

TierWorkers per PRConcurrent PRs
Starter41
Team84
Business32Unlimited

Shards are balanced with longest-processing-time (LPT) ordering from scenario_stats timing data.


Self-healing locator drift

When Playwright throws a locator timeout or visibility failure, Guard can enter a heal sub-loop:

  1. Capture DOM + screenshot
  2. Propose a new role-based locator (vision LLM when configured)
  3. Retry the step; if it passes → mark self_healed
  4. Open a PR against .molar/scenarios/{scenario}.molar.md with the locator update

Guard always opens a PR for scenario file changes — never silent healing. See Mender for application-code fixes.


PR gating dashboard

At app.molar.it/dashboard/guard:

PageWhat you see
Runs (run_type=pr)PR runs with status, duration, and cache hits
PR gatingRecent PR gate activity and check status
Run detailLive SSE log tail, failure artifacts, Mender panel

Filter runs by run_type=pr to see PR-only history.


Security notes

  • Webhook HMAC validation + Redis deduplication
  • Per-installation BullMQ queue partitions (no noisy-neighbor starvation)
  • Octokit throttling with retry; secondary GitHub rate limits are never auto-retried
  • GitHub App private key in your secrets manager — not in repo env vars

See Security for full hardening guide.


Next