Skip to content
Latchkey LogoLatchkey home

GitHub Actions stale action processes nothing, or stops early

When the GitHub Actions stale action processes nothing the cause is usually a documented default rather than a fault, and the default that catches most people is a cap of thirty operations per run. A negative day count is the other common one, and it does not mean debug: the action documentation says a negative value means never.

Four inputs on the stale action with their documented defaults and what each one does when unset
Four documented inputs with their published defaults. Three of the four do something surprising at a value somebody is likely to try.

What this error means

The scheduled job runs on time, finishes green, and the backlog is untouched. Or it works for a while and then appears to stop halfway, which is the version that generates the most confusion, because a job that processed some items clearly has the permissions and clearly parsed the configuration. Occasionally the opposite is reported, where far more items are swept than intended, and that is almost always a day threshold or an exempt label rather than anything about the action itself.

Illustrative inputs, quoted with the defaults from actions/stale action.yml
with:
  days-before-stale: -1
  days-before-close: -1
  operations-per-run: 30

The cap is the first thing to check

The action declares an input for the maximum number of operations per run, described as controlling rate limiting against the GitHub API, and its default is thirty. That is not thirty issues, it is thirty API operations, and labelling an issue and later closing it are separate operations. A repository with a backlog of several hundred stale candidates will therefore appear to stop partway through every run, and to a reader watching the issue list it looks exactly like the action giving up.

It is not giving up, it is doing the documented amount of work and stopping. On a daily schedule the backlog does eventually drain, slowly. Raising the number is the obvious response and it is the right one, with the caveat that the input exists because the API has limits, so raising it without watching for rate limiting simply moves the failure.

The second knob to read is whichever day threshold applies to what you are looking at. There are three pairs: a general one, an issues specific one and a pull request specific one, and the specific ones override the general one for their own kind. A workflow that sets the general value and then the issues value is not applying both, and reading only the general one will mislead you.

InputDocumented defaultWhat a surprising value does
operations-per-run30caps the work, so a backlog appears to stall
days-before-stale60a negative value means never mark, not debug
days-before-close7a negative value means never close
debug-onlyfalsetrue performs no operations on live issues

Common causes

The run hit the operations cap

Thirty operations by default, and marking and closing are separate operations. On any real backlog this is reached quickly, and the run ends green with most of the list untouched.

A day threshold is set to a negative value

A negative value means never, as the input description says. Setting the stale threshold that way switches marking off completely, which reads from outside as the action doing nothing.

A kind specific threshold is overriding the general one

The issues and pull request variants override the general value for their own kind. A workflow that sets both and expects the general one to apply to issues is reading the wrong number.

The exempt labels do not match real labels

The comparison is against label names as they exist in the repository. A near miss exempts nothing, which produces the opposite complaint: far more items swept than intended.

The job lacks write access

This fails differently, with an API refusal in the log rather than quiet partial work. In our experience it is the first thing people suspect and rarely the actual cause when the run is green.

How to fix it

Raise the operations cap deliberately

Set the input to a number that covers your backlog, and watch the first few runs for rate limiting. The default is low because the limit is real, so treat a large value as something to monitor rather than set and forget.

.github/workflows/stale.yml
- uses: actions/stale@v9
  with:
    operations-per-run: 200

Use the debug input for a dry run

  1. Set the debug only input to true and run the workflow by hand.
  2. Read the log to see which items the action selected.
  3. Set it back to false once the selection looks right.
.github/workflows/stale.yml
- uses: actions/stale@v9
  with:
    debug-only: true

Read the threshold that actually applies

Find the kind specific input first, because it overrides the general one. If only the general one is set, that is the number in effect for both issues and pull requests.

Declare the documented permission block

Copy the block the project documents rather than guessing. Content write access is only needed if you use branch deletion, so leave it out when you do not.

.github/workflows/stale.yml
permissions:
  actions: write
  issues: write
  pull-requests: write

Compare exempt labels against the real label list

Print the repository labels and check each exempt entry against that list rather than against memory. Label names are easy to get almost right, and almost right exempts nothing.

shell
gh label list --limit 200 --json name --jq '.[].name'

A negative day count means never, and it is not the dry run

This is the correction worth making carefully, because the wrong version of it is widespread. The action documentation for the day thresholds says that a negative value means never to mark or never to close automatically. It is a deliberate configuration for a repository that wants one half of the behavior and not the other, not a debugging mode and not a no operation value that leaves everything else untouched.

Used correctly it is genuinely useful. Setting the close threshold to a negative value gives you an action that marks items stale and never closes them, which is a reasonable permanent policy and also a reasonable way to watch what a new configuration would touch before letting it close anything. Setting the stale threshold to a negative value turns marking off entirely, which is the one that makes a run process nothing at all.

The real dry run is a separate input. The action declares one that runs the processor in debug mode without performing any operations on live issues, and its default is false. That is what you want when the question is what would this do, because it exercises the whole selection path without touching anything.

.github/workflows/stale.yml
- uses: actions/stale@v9
  with:
    days-before-stale: 60
    days-before-close: -1    # mark, never close
    operations-per-run: 200

- uses: actions/stale@v9
  with:
    debug-only: true        # the actual dry run

Permissions, and what the action documents

The project documents the permission block it expects, and it is longer than people assume. Write access to issues and to pull requests is needed to label and close them. Write access to contents is listed as needed only for the branch deletion option. Write access to actions is listed too, for the parts of the action that touch workflow state.

A missing permission does not look like the cap. The cap produces partial work with no complaint, and a missing permission produces an API refusal. If the log carries a refusal about a resource not being accessible, you are looking at the permission problem and nothing on the rest of this page applies. Our page on that message covers it directly.

Exempt labels are the last thing to check, and they are a string comparison against labels that actually exist. A label list that names something slightly different from the real label exempts nothing, which is the version of this that sweeps more than intended rather than less.

.github/workflows/stale.yml
permissions:
  actions: write
  contents: write   # only for the delete-branch option
  issues: write
  pull-requests: write

Why there is no recorded run on this page

A recorded run of this action would be a recording of our issue backlog on one day, and the whole subject is how the action behaves against the size and age distribution of somebody else backlog. Ours would be unrepresentative by construction, since a repository created to demonstrate the cap would have to be given several hundred aged issues first.

Every number and every meaning on this page instead comes from the action own declared inputs, where the default and the description sit together in the metadata file that GitHub reads. That is the same text the action ships with, so it stays true as the action changes in a way a screenshot would not.

Nothing here is repairable by a runner either. An action that stopped after its configured number of operations did what it was asked, and one that marked nothing because it was told never to mark is also correct.

How to prevent it

  • Set the operations cap explicitly so nobody has to remember the default.
  • Use the debug input for dry runs, and never a negative day count for that purpose.
  • Keep exempt label names in one place and check them when labels are renamed.
  • Read the kind specific day thresholds before the general one.

Frequently asked questions

Why does actions/stale stop partway through my backlog?
Because the operations per run input defaults to thirty and marking and closing each count as an operation. The run ends green having done the documented amount of work. Raise the input, and watch the first runs afterwards for API rate limiting.
Does days-before-stale set to -1 mean debug mode?
No. The input description says a negative value means never to mark issues or pull requests as stale automatically. It is a permanent configuration choice. The dry run is a separate input that runs the processor without performing operations on live issues.
How do I preview what the stale action would do?
Set the debug only input to true and run the workflow by hand. It exercises the same selection path and touches nothing, which is exactly what a preview should do. Setting a negative close threshold is a different thing: it marks items for real and never closes them.
Why did the stale action close issues it should have skipped?
Almost always because an exempt label name does not match a label that exists in the repository. The comparison is on the name as written, so a near miss exempts nothing and the item is treated as ordinary.

Related guides

References

A nightly job bills nightly, whether or not it moves the backlog. Latchkey bills at $0.0025/min. Start free → 30-day trial · No credit card