# PR Auto-Fix Dispatch — site / cause / gate reference

How the engine decides whether to auto-dispatch a fix or review agent in response to PR state. This document is the short reference table for "which flag gates which site"; for the full lifecycle narrative, read [`docs/pr-review-fix-loop.md`](pr-review-fix-loop.md), and for the polling side, read [`docs/auto-discovery.md`](auto-discovery.md).

All dispatch decisions happen inside `engine.js#discoverFromPrs` once per discovery tick. The evaluator walks PRs in the order documented in `pr-review-fix-loop.md` §4 and consults the per-cause gate flag together with the master kill-switch and per-provider polling gate.

## Dispatch sites

| # | Cause                  | Trigger (PR state)                                | Per-cause gate flag        | Hard-stop kill-switch | Other gates                                              |
|---|------------------------|---------------------------------------------------|----------------------------|-----------------------|-----------------------------------------------------------|
| 1 | Initial minion review  | `reviewStatus === 'pending'`                      | `autoReviewPrs`            | `autoFixPaused`*      | `evalLoop`; provider polling enabled                      |
| 2 | Human-feedback fix     | `humanFeedback.pendingFix` (or coalesced)         | `autoFixHumanComments`     | `autoFixPaused`       | provider polling enabled; `!awaitingReReview`             |
| 3 | Minion re-review       | `reviewStatus === 'waiting'` after fix push       | `autoReReviewPrs`          | `autoFixPaused`*      | `evalLoop`; provider polling enabled                      |
| 4 | Minion review-feedback fix | `reviewStatus === 'changes-requested'`         | `autoFixReviewFeedback`    | `autoFixPaused`       | `evalLoop`; provider polling enabled; `!awaitingReReview` |
| 5 | Build-failure fix      | `buildStatus === 'failing'` + grace expired       | `autoFixBuilds`            | `autoFixPaused`       | provider polling enabled; `_buildFixPushedAt` grace       |
| 6 | Merge-conflict fix     | `_mergeConflict === true` + `status === 'active'` | `autoFixConflicts`         | `autoFixPaused`       | provider polling enabled; `!fixDispatched` in same pass   |

\* `autoFixPaused` is the auto-**fix** kill-switch. It forces sites 2, 4, 5, and 6 off, but is **not** wired to suppress review dispatch (sites 1 and 3) — review verdicts stay fresh during a fix-storm incident. To halt review dispatch, clear `autoReviewPrs` / `autoReReviewPrs` directly, or set `pollingPaused: true` (which forces every per-PR auto-dispatch gate inert by zeroing `pollEnabled`).

### Provider polling gate

Each PR carries a `repoHost` (`github` / `ado`). Before evaluating any of the six sites, `discoverFromPrs` resolves a `pollEnabled` boolean from that provider's poll gate:

- GitHub PRs honor `ghPollEnabled` (legacy macro) and the new granular `ghPrStatusPollEnabled` / `ghPrCommentsPollEnabled` / `ghPrReconcileEnabled`.
- ADO PRs honor `adoPollEnabled` and `adoPrStatusPollEnabled` / `adoPrCommentsPollEnabled` / `adoPrReconcileEnabled`.

When the provider's status poll is off, the cached `buildStatus` / `reviewStatus` / `_mergeConflict` won't refresh — but `discoverFromPrs` still runs against the **last known cache** unless `pollingPaused` is also set, which forces `pollEnabled=false` and makes every per-PR auto-dispatch gate inert.

See [`docs/auto-discovery.md`](auto-discovery.md) → "Granular per-poller flags" for the full polling-side table and the resolution order.

## Kill-switch hierarchy

There are two independent operator-facing master flags:

| Flag             | Default | Effect                                                                                                                                                                                                                                                                                          |
|------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `pollingPaused`  | `false` | Skips `pollPrStatus` + `pollPrHumanComments` for both providers. Forces `pollEnabled=false` in `discoverFromPrs` so every per-PR auto-dispatch gate (`autoReviewPrs` / `autoFixBuilds` / …) becomes inert. Reconciliation (recovery sweep) intentionally still runs. Default OFF — fresh installs behave identically. |
| `autoFixPaused`  | `false` | Narrower: forces `autoFixBuilds` / `autoFixConflicts` / `autoFixReviewFeedback` / `autoFixHumanComments` to `false` for every project. Review dispatch, polling, and reconciliation are intentionally not gated, so operators can stop a fix-storm while still seeing fresh verdicts and build status. Default OFF. |

These compose: setting `pollingPaused: true` plus `autoFixPaused: true` halts *all* automated PR work (review + fix + polling). Setting only `autoFixPaused: true` halts only auto-fix dispatch. Setting only `pollingPaused: true` halts polling and effectively halts dispatch (because the per-PR gates rely on cached poll state), but leaves reconciliation running and lets manual `/api/pull-requests/observe` or operator-driven dispatch still proceed.

Flip via Dashboard → Settings → Polling (`pollingPaused`) / Auto-fix & Review Loop (`autoFixPaused`), or set `engine.<flag>: true` in `config.json`.

## Per-PR per-cause pause (different mechanism)

Independent of the master kill-switches, each PR tracks repeated no-op fix outcomes per cause in `pr._noOpFixes[<cause>]`. When the same fix dispatch returns "nothing to fix" multiple times against the same evidence fingerprint, `recordPrNoOpFixAttempt` flips `_noOpFixes[<cause>].paused = true` for that **single PR + cause** pair. The dashboard renders a red `_pausedCauses` chip; the recovery paths are:

1. Push a new SHA to the PR branch (fingerprint shifts → `clearPrNoOpFixAttempt` on next non-noop completion, or proactive GC in `pollPrStatus`).
2. Click the red chip → `POST /api/pull-requests/clear-paused-cause` with `{ prId, cause }`.
3. Direct API call to the same endpoint.

This per-PR per-cause pause is unrelated to `autoFixPaused`. Cause keys are validated against `shared.PR_FIX_CAUSE` (e.g. `BUILD_FAILURE`, `REVIEW_FEEDBACK`, `HUMAN_FEEDBACK`, `MERGE_CONFLICT`, `PR_FIX`). Full mechanics in [`docs/pr-review-fix-loop.md`](pr-review-fix-loop.md) §4E.

## Key files

| File                      | Role                                                                                              |
|---------------------------|---------------------------------------------------------------------------------------------------|
| `engine.js#discoverFromPrs` | Single dispatch site walker — evaluates the six causes per tick in fixed order                  |
| `engine/shared.js` (`ENGINE_DEFAULTS`) | Default values for every per-cause gate + both kill-switches                         |
| `engine/lifecycle.js#runPostCompletionHooks` | Wires `autoReReviewPrs` into the closure-loop re-review path (P-e8b1c4d2)         |
| `dashboard/js/settings.js` | Auto-fix & Review Loop pane (gates + `autoFixPaused`); Polling pane (`pollingPaused` + granular polls) |
| `dashboard/js/render-dispatch.js` | `renderPausedBanner(engine)` — sticky cross-page banner showing both pause states          |
| `POST /api/pull-requests/clear-paused-cause` | Clears a per-PR per-cause pause record                                          |
| `POST /api/engine/polling/{pause,resume}`, `POST /api/engine/auto-fix/{pause,resume}` | Convenience endpoints for the two master flags (P-f3c9d0e7) |
