Skip to main content

Runaway CI

A coding agent that pushes, re-runs or opens pull requests in a loop can use up your runner minutes in hours, and nothing tells you until the 80% usage mail. fremforge watches for it.

What is measured

Every 5 minutes, for every organisation, fremforge looks at the last 60 minutes of Actions runs on its hosted runners and measures:

SignalWhatLine
Runs per personRuns one person started in the organisationAbove 3 times that person’s busiest normal hour, and never below 80
Runner minutes per personRunner minutes those runs usedAbove 3 times that person’s busiest normal hour, and never below 600
Pushes to one pull requestDifferent commits one person pushed to one pull requestMore than 12

“That person’s busiest normal hour” is their 99th-percentile hour over the last 14 days, recomputed once a day. A person with less than 20 active hours in that time is measured against the organisation’s busiest normal hour instead. So a team whose agents run CI all day is measured against its own normal, not against a quiet team’s.

The pull request line counts pushes, not runs: one push starts as many runs as the repository has workflows for it.

The subject is always a person. A run an agent starts on someone’s behalf is that person’s run (the run’s trigger user), and there are no agent accounts. Bots, such as Renovate, are measured the same way and marked as bots. Runs fremforge starts itself, such as lockfile refreshes, are not measured.

Calibration

The lines were set on a month of CI where most runs come from coding agents. Replayed through the detector, that month raises no runaway once each person has 14 days of history.

What happens

When a figure crosses its line, fremforge records a runaway, once:

  • A mail to every owner of the organisation naming the person, where (the organisation or one pull request) and the figures.
  • A mail to the person whose runs they are, written to them, so they can stop their session. Not for a bot, and an owner whose runs they are gets one mail, not two.
  • A ci.runaway event on the event feed, one per repository the runs are in, so an agent session reading the feed sees it too.
  • An audit event (ci_runaway.detected).
  • A row on the CI usage page (see In the console).

While the figure stays over the line, the runaway is refreshed, not mailed again. It ends by itself after 60 minutes back under the line.

Nothing is stopped unless your organisation has the pause on.

The pause (opt-in)

With the pause on, a runaway also stops fremforge’s runners from starting that person’s new runs, or for a pull request runaway, that person’s new runs on that pull request, until an admin resumes it. The pause is off by default.

What the pause never does:

  • It never cancels anything. A held run waits in the queue, shown on Explore → Actions as paused, and starts within seconds of a resume.
  • It never stops a run that has started. A run with a job already running finishes, including its later jobs.
  • It never holds anybody else. Only runs triggered by the paused person (on the paused pull request) wait.
  • It fails open. If fremforge cannot read the pause, runs start as usual.

It holds fremforge’s hosted runners only. A runner your organisation runs itself can still pick the job up.

A paused runaway does not end by itself. An owner resumes it, or turns the pause off, which resumes every paused runaway at once. After a resume, the same person is not flagged again for 60 minutes.

Forgejo’s scheduled workflows (the forgejo-actions user) are measured and alerted on, but never paused.

In the console

Org owners find runaways at Build & deploy → CI usage, section Runaway CI (https://frem.sh/<org>/_admin/ci-usage#runaways): the pause switch, then each runaway with its person, where, its busiest hour, its state and a Resume (or Dismiss) button. A banner at the top of the page says when one is live.

API

The token needs runners:read to list and runners:write to resume or switch the pause. Every change is audited as the person the caller acts for.

# List (state=live for open and paused only)
curl https://frem.sh/_app/api/v1/orgs/<org>/ci-runaways \
  -H "Authorization: Bearer $FREMFORGE_TOKEN"

# Turn the pause on (or off: off resumes every paused runaway)
curl -X PUT https://frem.sh/_app/api/v1/orgs/<org>/ci-runaways/settings \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pause": true}'

# Resume one (or close an open one)
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/ci-runaways/<id>/resume \
  -H "Authorization: Bearer $FREMFORGE_TOKEN"

A runaway in the list:

{
  "id": "4f0c…",
  "status": "paused",
  "scope": "pr",
  "person": "alice",
  "person_kind": "person",
  "repo": "api",
  "pr_number": 42,
  "signals": ["pr_pushes"],
  "runs": 48,
  "heads": 16,
  "minutes": 0,
  "detected_at": "2026-10-25T09:40:00.000Z",
  "paused_at": "2026-10-25T09:40:00.000Z",
  "resumed_at": null
}

status is open (alerted), paused, ended or resumed. The MCP server lists them with list_ci_runaways.

Stopping the loop itself

The alert tells you which person’s work is looping; the session is theirs. Stop the agent session, or revoke the token it uses. Under on-behalf-of tokens, revoking the delegated token stops that session without touching the person’s other work.