Skip to main content
Issues and pull requests pile up. Some are finished but nobody closed them; some wait on an answer that never comes. The stale feature finds the ones nobody has touched for a while and labels them stale, with a friendly comment asking whether they are still needed. If they stay quiet after that, it can label them abandoned and close them. You would use it to keep your issue list honest: what is open is what is being worked on. Anyone can keep an item alive just by commenting on it or pushing to it. The feature runs as a sweep: once per scheduled or manual run, it looks at every open issue and pull request in the repository.

Turn it on

1

Run smartcloud on a schedule

The sweep runs only on schedule and workflow_dispatch events, never on pushes or pull request events. Make sure your smartcloud workflow has both:
.github/workflows/smartcloud.yml
See Getting started for the whole workflow.
2

Give the job write access to issues and pull requests

Labelling, commenting and closing need issues: write and pull-requests: write on the job. The workflow token is enough; no app or personal token is needed.
3

Add a stale section

The smallest useful section names how many days of silence make an item stale, and which label marks it:
.github/smartcloud.yml
Always set staleComment: it is how smartcloud remembers when it marked an item (see How it works).
4

Run it once by hand

Open the Actions tab, pick the smartcloud workflow and choose Run workflow. Check the job summary to see what was marked. Set the action’s dryRun: true input first if you want to see what would happen without changing anything.

Options

staleLabel, abandonedLabel and exempt.labels are label names as GitHub shows them, not keys of the labels section. If you also manage labels with the labels feature, define them there too so they get a colour and description. To sweep only issues:
To keep RFCs open however long they sit, whatever their labels:

Complete example

.github/smartcloud.yml

How it works

Every sweep:
  1. Lists every open issue and pull request, keeps the kinds named in on, and drops any with an exempt.labels label or that pass exempt.when.
  2. For each item without the stale label: when it has had no activity for staleAfterDays, and it is not snoozed, smartcloud posts staleComment and then adds the stale label.
  3. For each item with the stale label:
    • If it also has the abandoned label, it is left alone.
    • If it has had activity since it was marked, or is snoozed, the stale label comes off.
    • Otherwise, when it has been quiet for abandonedAfterDays, smartcloud adds the abandoned label, posts abandonedComment, and closes it when close is true.
“Activity” is GitHub’s updated_at time: any comment, push, edit, label or review. smartcloud’s own label and comment update it too, so abandonedAfterDays counts from the stale mark, not from the last human activity. Changes within ten minutes of the mark are treated as smartcloud’s own and do not count as activity. The stale comment carries a hidden record of when the item was marked. That record is how a later sweep tells new activity from the mark. Without a staleComment there is no record, and the stale label stays until someone removes it by hand. smartcloud keeps at most one stale and one abandoned comment per item, and edits an existing one rather than posting again. Only a marker comment written by a bot account or by a login in roles.trustedBots counts, for both editing and reading the mark time, so a person cannot take over the comment or forge when an item was marked. The stale comment is written before the stale label, so if GitHub fails part way the next sweep marks the item again. Each item is swept on its own. When GitHub fails on one, the sweep records an error naming it and carries on with the rest.

Snoozing

With the commands feature on, anyone with the triage role can comment /stale-snooze [days] on an item. It keeps the item from being marked stale until the snooze ends, and takes off the stale label if it has one. The number of days is 1 to 365; without one, commands.snoozeDays is used (30 by default). smartcloud records the end of the snooze in one comment of its own on the item, which a later snooze updates. Only a snooze comment written by a bot account or a roles.trustedBots login counts, so nobody can snooze an item by typing the marker.

Exemptions that need extra data

When exempt.when needs a pull request’s files, reviews, requested reviewers, commits, mergeability or checks, the sweep loads them for each pull request it considers, which takes more API calls. A sweep cannot read a pull request’s draft state, branches or size. So if exempt.when uses isDraft, branchMatches, baseBranchMatches or changesSize, the sweep records a warning and skips every pull request rather than act on one that should be exempt. Issues are swept as usual, since those conditions never hold for an issue.

What you will see on GitHub

  • On the item: the stale label and your staleComment from the account smartcloud acts as (for example github-actions[bot]). Later, the abandoned label, abandonedComment, and the item closed if close is on.
  • Check run: smartcloud / stale on the commit the run is for, the default branch’s head on a schedule. It concludes success with no problems, neutral with warnings, and failure when an item could not be swept or the config is wrong. See check runs.
  • Job summary: every label, comment and close it made, as a list of changes, plus any warnings and errors.
  • Notifications: with the notifications feature, items that were marked, abandoned or closed can be sent to Slack, Discord or Linear.

Forks and restricted runs

The sweep only runs on schedule and workflow_dispatch, which never come from a fork, so it always runs with the repository’s own token. It works with the plain workflow token. If a write is still refused, for example because the job lacks issues: write, that write is skipped and listed under Restricted access in the job summary in a restricted run, or recorded as an error for that item otherwise.

Troubleshooting

That is expected. The sweep runs only on schedule and workflow_dispatch events. Add a schedule trigger to the workflow, or run it by hand from the Actions tab.
The two labels have the same name, ignoring case, so a stale item would look abandoned. The sweep changes nothing until you give them different names. Remember abandonedLabel defaults to abandoned, so staleLabel: Abandoned clashes with it.
smartcloud only knows when it marked an item from its staleComment. Without a staleComment, or when the comment was deleted or written by a person rather than a bot, it cannot tell new activity from the mark, and the label stays. Set staleComment; remove the label by hand for items already marked.
A sweep cannot read those for a pull request, so every pull request is skipped to avoid acting on one that should be exempt. Use a label in exempt.labels instead, or a condition the sweep can answer, such as titleMatches or hasLabel.
Someone removed the stale label between the listing and the sweep’s own removal. Nothing is wrong; the item is unmarked either way.
GitHub refused or failed a request for that one item. The rest of the sweep carried on. Common causes are a missing issues: write or pull-requests: write permission, or GitHub rate limits on a large repository; the next sweep tries again.
close is false by default. Set close: true.
Last modified on September 27, 2026