How to Close Stale Issues Automatically in GitHub Actions
A backlog full of abandoned issues hides the live ones; a scheduled stale sweep warns then closes the dead weight.
Run actions/stale on a cron schedule with day thresholds for marking and closing, plus exempt labels for items you never want auto-closed.
Steps
- Add a
scheduletrigger (e.g. daily). - Configure days-before-stale and days-before-close.
- Set warning messages for issues and PRs.
- Exempt important items with
exempt-issue-labels.
Workflow
name: Stale
on:
schedule:
- cron: '0 1 * * *'
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
days-before-stale: 60
days-before-close: 14
stale-issue-message: 'No activity in 60 days; closing in 14 if nothing changes.'
exempt-issue-labels: 'pinned,security'Verify it actually works
A workflow that runs is not a workflow that works. Confirm the behaviour on a real event rather than on a manual dispatch, because trigger conditions, permissions, and context values all differ between the two.
# 1. validate the file before pushing
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# 2. trigger the real event, not workflow_dispatch
git commit --allow-empty -m "ci: verify trigger" && git push
# 3. watch it and read the conclusion, not just the colour
gh run watch
gh run view --log-failedWhat usually goes wrong first
- The workflow file must exist on the default branch before scheduled or dispatch triggers appear at all.
GITHUB_TOKENpermissions default to read-only in many organisations. Declare apermissions:block listing every scope the job needs.- Fork pull requests get a read-only token and no access to secrets, regardless of workflow configuration.
actions/checkoutgives you depth 1 on a detached HEAD, so anything needing history or a branch name needsfetch-depth: 0.