Watches the workflows of every repository the token can see, or of one organization or user when the connection names one. Repositories with no push in ninety days, archived ones and disabled ones are skipped.
A fine grained personal access token with Actions read and write and Metadata read on the repositories to watch, or a classic token with the repo scope. Or sign in: the device flow, or the browser flow once an OAuth App is registered. See Authentication.
A fine grained token also needs Pull requests read and Contents read to say what became of a failed branch, under Rows; without them a failed branch goes only once the default branch has built since.
For GitHub Enterprise Server enter the server URL; the API is reached under /api/v3.
One per workflow, showing its latest run on the repository's default branch, which the repository listing names. A run on another branch that is running, queued or failed gets a row of its own. Pull request runs link to the pull request. A run from a fork is shown on the fork's branch behind its owner, someone:main, so a fork's main is never taken for the repository's, and its branch opens in the fork. GitHub leaves the pull request off a run from a fork, so that row has no pull request button.
A failed branch whose pull request has been merged or closed, or which has been deleted, loses its row (see the window). The connection asks GitHub, for every failed branch of a repository on its host, whichever service built it: an AppVeyor, Travis CI or Azure Pipelines build of a GitHub repository is asked about here too. A pull request is asked for its state; a fork's branch, whose run names no pull request, for the newest pull request from it; any other branch whether it, or a tag of its name, is still there, since a release workflow's run is named for its tag. A pull request found open or a branch found there is asked about again after ten minutes, and one the token may not see after an hour. Merged, closed or deleted is not asked again, unless the branch builds again.
- Retry re-runs only the failed jobs of a failed run, and the whole run otherwise
- Cancel cancels a queued or running run
- Log copies the logs of the jobs that failed or timed out in the latest attempt
A classic token or a sign in lists its scopes, and without repo its rows offer neither Retry nor Cancel; the connection shows as watch only in Connections. With repo, a repository the user can only read offers neither. A fine grained token lists nothing, so its rows offer both, and a refusal says what the token needs.
GitHub gives none. The countdown comes from the median of the workflow's last ten successful runs.
Runs are fetched a repository at a time, each on its own schedule (see Poll intervals), with a conditional request: GitHub answers an unchanged repository with a 304 that does not count against the five thousand requests an hour. GitHub also limits requests a minute and counts those 304s, so no more than 450 requests a minute are sent, the most urgent repositories first. The hourly limit is shared with every other tool signed in as the same account.
Each poll interval, page 1 of the repository list is read again, most recently pushed first, with the same conditional request discovery uses, so it costs nothing while nothing is pushed. A repository whose last push moved is fetched at once rather than when its schedule comes round. Runs that start without a push, such as scheduled runs, manual dispatches, re-runs started on the web and pull requests from forks, wait for the schedule: up to five minutes on a quiet repository.
A repository's page of runs holds five a workflow, and a batch of pull requests can fill a workflow's five. Its latest run on the default branch is kept past the five where the page holds it, and where it does not, that workflow's runs on the default branch are asked for, with the same conditional request. A workflow with none since the history cutoff, such as one only pull requests trigger, is not asked again for an hour.
Discovery lists a repository's workflows again only once it has been pushed to since they were last listed, and every repository's once an hour, so a workflow enabled or disabled without a push can take up to an hour to show or go.
After each cycle the connection asks about the failed branches due an answer, one question a branch however many workflows failed on it, up to twenty a cycle and eight at a time. They are conditional requests too, count towards the same 450 a minute, and wait while that is spent. A question that fails is asked again after a minute, doubling, and leaves the connection's health alone.
---
config:
flowchart:
wrappingWidth: 400
---
flowchart TD
wake(["Wake: something is due,<br/>or Refresh, Retry or Cancel"]) --> listed{"Listed repositories in<br/>the last 10 minutes,<br/>with this token?"}
listed -- "no" --> discover["GET user for the token's<br/>scopes, then user/repos?sort=pushed<br/>or the owner's, up to 5 pages,<br/>then the workflows of each<br/>repository pushed since they<br/>were listed, or of every one<br/>pushed in 90 days each hour"]
listed -- "yes" --> probed{"Probed in the<br/>last 30 seconds?"}
probed -- "no" --> probe["GET page 1 of the same list,<br/>a free 304 while<br/>nothing was pushed"]
probe --> moved{"A repository's<br/>pushed_at moved?"}
moved -- "yes" --> nudge["Fetch that repository<br/>now, then every 30 s<br/>for 3 minutes"]
discover --> interval["Each repository is polled<br/>as its busiest workflow needs"]
probed -- "yes" --> interval
moved -- "no" --> interval
nudge --> interval
interval --> finishing["Running, from its fastest<br/>recent run to 90 s past<br/>its slowest, or with no<br/>history: every 10 s"]
interval --> running["Running for less than its<br/>fastest recent run, or<br/>queued: every 30 s, and<br/>again when it reaches that"]
interval --> overrun["Running over 90 s past<br/>its slowest recent run:<br/>the time beyond that ÷ 10,<br/>30 s to 5 minutes"]
interval --> quiet["Quiet: the time since<br/>the last run ÷ 30,<br/>30 s to 5 minutes"]
interval --> failed["Quiet after a failure:<br/>the time since the<br/>last run ÷ 120,<br/>30 s to 5 minutes"]
finishing --> stretch["Up to 8 times longer while<br/>under a quarter of the<br/>hourly limit is left"]
running --> stretch
overrun --> stretch
quiet --> stretch
failed --> stretch
stretch --> due{"Due, and within<br/>450 requests a minute?"}
due -- "no" --> sleep(["Sleep until a repository,<br/>the probe or the<br/>listing is due"])
due -- "yes, most urgent first" --> fetch["GET repos/{owner}/{repo}/actions/runs<br/>with If-None-Match,<br/>8 at a time"]
fetch -- "200 or 304" --> held{"A run on the default<br/>branch for each workflow,<br/>or none in the last hour?"}
held -- "yes" --> rows["Update its rows"]
held -- "no" --> workflow["GET repos/{owner}/{repo}/actions/<br/>workflows/{id}/runs?branch={default}<br/>for each workflow without one"]
workflow --> rows
fetch -- "429, or 403 from<br/>a secondary limit" --> pause["Pause the connection<br/>as long as GitHub asks,<br/>or from a minute, doubling"]
fetch -- "other failure" --> backoff["Back off that repository,<br/>doubling up to 10 minutes"]
rows --> branches{"A failed branch of a<br/>repository on this host,<br/>from any service, not<br/>answered for good?"}
branches -- "yes, up to 20" --> ask["GET its pull request, the newest<br/>pull request from a fork's branch,<br/>or the branch, then tags of its name"]
branches -- "no" --> sleep
ask --> sleep
pause --> sleep
backoff --> sleep
Researched 2026-09-14. [live] means checked with requests against api.github.com; [docs] names the evidence. See Provider APIs for every provider side by side.
- REST allows 5,000 requests an hour per user, shared by every OAuth app and personal access token of that user; 15,000 for apps owned by an Enterprise Cloud organization; 60 unauthenticated [docs].
- Headers:
x-ratelimit-limit,x-ratelimit-remaining,x-ratelimit-used,x-ratelimit-reset(Unix seconds) andx-ratelimit-resource[docs, live]. - Secondary limits: 100 concurrent requests, 900 points a minute for REST where a GET costs 1, 90 seconds of CPU per 60 seconds, and 80 content creating requests a minute [docs].
- Either limit answers 403 or 429. Honour
retry-after; if remaining is 0, wait until the reset; otherwise wait at least a minute and back off exponentially. Continuing while limited risks the integration being banned [docs]. - GraphQL has its own 5,000 points an hour, and a secondary limit of 2,000 points a minute [docs].
- Lists carry weak ETags. A request with
If-None-Matchanswered 304 and leftx-ratelimit-remainingunchanged, on bothactions/runsandorgs/{org}/repos[docs, live]. It is only free when the request carries theAuthorizationheader. - The docs do not exempt a 304 from the secondary limits.
- ETags are per page: a 304 for page 1 says nothing about page 2.
- Lists send
Cache-Control: private, max-age=60, and event feeds sendX-Poll-Interval: 60[live].
user/repos,orgs/{org}/reposandusers/{user}/reposacceptsort=pushedand returnpushed_at, so page 1 holds the most recently pushed repositories and answers 304 while nothing changed [docs, live].users/{user}/reposlists public repositories only.pushed_atdoes not move for scheduled runs, manually dispatched runs, re-runs, or pull requests from forks.
- GraphQL can batch: one query returned the head commit and check suites of 75 repositories for 1 point, and
nodes(ids:)over workflownode_ids returnsruns(first: N)for up to 100 workflows for 1 point [live]. WorkflowRunhas no status, conclusion, branch or start time of its own. They come from itscheckSuite(status, conclusion, branch, matchingPullRequests) and that suite'scheckRuns(startedAt, completedAt) [live].- GraphQL has no conditional requests.
actions/runsfilters on actor, branch, event, status, created,exclude_pull_requests,check_suite_idandhead_sha, and returns at most 1,000 results when filtered [docs].
Checked 2026-09-16.
X-OAuth-Scopeslists the scopes of an OAuth App's token or a classic token, comma separated:gist, read:org, repo, workflowonGET user[docs, live]. The documentation describes it for no other token, so its absence says nothing.- Re-running a run, re-running its failed jobs and cancelling it need the
reposcope on those tokens, and Actions write on a fine grained one [docs]. With no repository scope a token reads public information only [docs]. Whetherpublic_repoalone is enough is not documented. - The repository lists carry
permissions, withpushfor write access [docs], which re-running and cancelling need. Whether a fine grained token's are its user's or its own is not documented. X-Accepted-GitHub-Permissionsnames the permissions an endpoint requires, not those a token holds [docs].GET repos/{owner}/{repo}/pulls/{number}and the pull request list need Pull requests read on a fine grained token;branches/{branch}andgit/matching-refs/{ref}need Contents read [docs].
Checked 2026-09-23.
- A pull request carries
state,openorclosed, andmerged_at, set once merged [docs, live]. - The pull request list filters on
head=owner:branch, a fork's too, andstate=allkeeps the closed ones; the newest comes first [docs, live]. A fork's run names no pull request, so this is how one is found. branches/{branch}takes a branch with slashes in it unescaped, and answers 404 for a branch that is not there and for a repository the token cannot see alike [live].git/matching-refs/tags/{name}lists every tag starting with the name, sov1listsv1.1; it answers[]for a repository the token can see with no such tag, and 404 for one it cannot [live].
- The Events API.
users/{user}/events/orgs/{org}includes private events, but events arrive 30 seconds to 6 hours late, only 300 events or 30 days are kept, there are no workflow run, check or status events, and a PushEvent carries onlyrepository_id,push_id,ref,headandbefore[docs]. - Search commits. It has its own limit of 30 requests a minute, covers the default branch only, has undocumented index lag, and commits are not runs [docs].