Skip to main content
Fixing findings one repo at a time doesn’t scale past a handful of repositories. A bulk remediation campaign applies Pipefort’s auto-fixes across every repository you select in one action: the campaign scans each repo’s workflow files, opens (or reuses) a fix pull request per remediable file, and records the outcome per repository in a tracker you can export as CSV.

Launching a campaign

On the Remediation page:
  1. Click New campaign in the toolbar.
  2. Name it (e.g. Pin actions to SHA — Q3).
  3. Select the repositories to include.
  4. Click Open PRs in N repos.
The browser then drives the campaign the same way org-wide scanning works: one short API request per repository, a few repos in flight at a time, so each request stays well within serverless limits. You can watch the tracker fill in live as repos complete.
Admin only. Launching a campaign — and remediating repos within one — is restricted to organization admins, since it opens pull requests across the org’s repositories. Any member can view campaign trackers.

What gets fixed

Campaigns remediate workflow-YAML findings that have an auto-fix — the same fixer registry behind the per-finding Fix button (pinning actions to SHAs, adding least-privilege permissions, adding timeouts/concurrency, removing debug logging, and so on). For each repository, the campaign:
  • fetches and scans the repo’s CI workflow files in memory (nothing is cloned),
  • honors your rule settings — disabled rules are never “fixed”,
  • opens one pull request per remediable workflow file — every fixable finding in that file is applied in a single commit, so a workflow with five issues gets one PR to review, not five — or reuses the existing Pipefort fix PR if one is already open,
  • records the outcome.
Repo-settings findings (branch protection, secret scanning, …) are not included — those are one-click direct fixes on the repository page, not pull requests.

Per-repo status

The campaign itself is running until every repo reaches a terminal state, then completed. The queue opens on your most recent campaign; the picker in the toolbar switches between them, and the tabs filter by outcome — all, with PRs, nothing to fix, failed — each showing its own count. CSV downloads the whole table: one row per repository with its status, PR count, fixes applied, PR URLs, and any error.
Partial is not success. A repository that opened a PR and reported an error got part of the way — the row is amber, not green, and names what was refused. Reading it as done leaves real findings unfixed.

The fix tracker

Above the campaign queue, Remediation tracks every fix pull request Pipefort has opened — from a campaign or from a single finding — in three states: Merged but the finding came back is the row worth watching. Somebody believed the problem was fixed and a later scan disagreed, which is a different and more expensive failure than a fix nobody merged. The header reports how many need a person, how many landed, and the median time from opening to merge.

Why a fix is stuck

What is happening inside an open pull request does not arrive on any webhook Pipefort receives: mergeability is computed by GitHub only when asked, and check conclusions and requested reviewers belong to event types the App does not subscribe to. So it is polled, by the daily GET /api/cron/refresh-fix-prs, and each row shows what GitHub said when it was last asked. When several are true at once the row shows the most blocking one, because the question it answers is “what is the next thing somebody has to do” — a pull request with a conflict and a red check is a conflict, since resolving it re-runs the checks.
A poll is a photograph. Every row is only true as of the moment it was taken, which is why the footnote states how recently. If the cron has never run, rows say “opened, no outcome yet” and the footnote says nothing has looked inside them — that is deliberately not the same as a clean bill of health.
On a deployment with no pull-request webhook configured, no outcome is ever recorded: every fix stays in flight, and nothing is called stale. Blaming a reviewer for a missing integration would be the wrong sentence.
A merge is not a resolution. The finding stays open and stays in the posture counts until a later scan stops seeing it. A fix made by hand, outside Pipefort entirely, closes a finding the same way.

What the campaign queue cannot tell you

The queue below the tracker is about the moment the campaign ran — what it managed to open, and what it could not. What became of each PR afterwards is the tracker’s job, not the queue’s.

Retrying

Launching a campaign never opens the same pull request twice: each repository is claimed before its fixes run, so a double-click, a page refresh mid-run, or a second admin working in another tab won’t duplicate the work. Repositories that failed can be retried — relaunching remediation for them picks the campaign back up and returns it to running. Repositories that already finished are left alone.

GitLab

Campaigns are provider-neutral: for GitLab-hosted repositories the campaign opens merge requests through the same flow.

API

Campaigns are driven by three endpoints — see the API reference:
  • POST /api/campaigns — create a campaign over selected repos (admin only).
  • POST /api/campaigns/{id}/repos/{repo_id}/remediate — remediate one repo (admin only; the SPA loops this with bounded concurrency).
  • GET /api/campaigns/{id} — the tracker: campaign, per-repo statuses, and the rollup.
Campaign rows are also readable directly from the database (remediation_campaigns, campaign_repos), scoped to your organizations.