|
| 1 | +# stale |
| 2 | + |
| 3 | +Marks issues and pull requests that have gone quiet, then closes them if they |
| 4 | +stay quiet. A thin, opinionated wrapper around |
| 5 | +[`actions/stale`](https://github.com/actions/stale). |
| 6 | + |
| 7 | +## Usage |
| 8 | + |
| 9 | +The caller owns the schedule. |
| 10 | + |
| 11 | +```yaml |
| 12 | +name: Stale |
| 13 | + |
| 14 | +on: |
| 15 | + schedule: |
| 16 | + - cron: '0 3 * * *' |
| 17 | + workflow_dispatch: |
| 18 | + |
| 19 | +permissions: {} |
| 20 | + |
| 21 | +jobs: |
| 22 | + stale: |
| 23 | + runs-on: ubuntu-latest |
| 24 | + timeout-minutes: 10 |
| 25 | + permissions: |
| 26 | + actions: write |
| 27 | + issues: write |
| 28 | + pull-requests: write |
| 29 | + steps: |
| 30 | + - uses: TrogonStack/github-actions/actions/stale@<sha> # vX.Y.Z |
| 31 | +``` |
| 32 | +
|
| 33 | +`actions: write` is not optional. Upstream keeps its progress in the Actions |
| 34 | +cache so a run capped by `operations-per-run` resumes where the last one |
| 35 | +stopped, and managing that cache entry needs the permission. Without it the |
| 36 | +cache delete fails with a 403, every run replays the same prefix of the |
| 37 | +backlog, and anything past that prefix is never closed. |
| 38 | + |
| 39 | +Run it once with `debug-only: true` before pointing it at a repository with |
| 40 | +years of backlog, so the first real run is not a surprise. |
| 41 | + |
| 42 | +## Labels |
| 43 | + |
| 44 | +| Label | Meaning | |
| 45 | +| --- | --- | |
| 46 | +| `stale:discard` | Applied by this action. Any activity removes it. | |
| 47 | +| `stale:keep` | Applied by a human. Exempts the issue or pull request forever. | |
| 48 | + |
| 49 | +Neither label needs to exist beforehand; `stale:discard` is created on first |
| 50 | +use. Create `stale:keep` yourself so it is available in the label picker. |
| 51 | + |
| 52 | +The names are not inputs. One vocabulary across every repository is the reason |
| 53 | +this action exists, and a rename in one repository is the drift it was built to |
| 54 | +remove. |
| 55 | + |
| 56 | +## Inputs |
| 57 | + |
| 58 | +| Input | Default | Description | |
| 59 | +| --- | --- | --- | |
| 60 | +| `days-before-stale` | `60` | Days without activity before marking stale. | |
| 61 | +| `days-before-close` | `7` | Days after marking before closing. `-1` marks but never closes. | |
| 62 | +| `operations-per-run` | `100` | API operations budget for one run. A run that hits the cap resumes from where it stopped. | |
| 63 | +| `debug-only` | `false` | Report what would happen and change nothing. | |
| 64 | +| `token` | `github.token` | Needs write access to actions, issues, and pull requests. | |
| 65 | + |
| 66 | +## Fixed behaviour |
| 67 | + |
| 68 | +These are not inputs, on purpose. |
| 69 | + |
| 70 | +- The label names are `stale:discard` and `stale:keep` everywhere. |
| 71 | +- Activity removes the stale label. Activity is the entire signal the action |
| 72 | + runs on, so it has to be able to clear it. |
| 73 | +- Issues close as `not_planned`, which avoids the completed badge on something |
| 74 | + nobody did. |
| 75 | +- Anything on a milestone is exempt. Work on a milestone is planned work, |
| 76 | + however quiet it has gone. |
| 77 | +- Oldest first, so a capped operations budget is spent on the worst offenders |
| 78 | + rather than on whatever is newest. |
| 79 | +- Branches are never deleted. Deleting someone else's branch on a timer is not |
| 80 | + ours to do. |
| 81 | + |
| 82 | +If you need one of these to differ, that is a conversation about the default |
| 83 | +rather than an input to add. |
| 84 | + |
| 85 | +## Outputs |
| 86 | + |
| 87 | +| Output | Description | |
| 88 | +| --- | --- | |
| 89 | +| `staled-issues-prs` | JSON array of what was marked stale this run. | |
| 90 | +| `closed-issues-prs` | JSON array of what was closed this run. | |
| 91 | + |
| 92 | +## Messages |
| 93 | + |
| 94 | +Closing is not a verdict, and the posted messages say so. What they ask for |
| 95 | +differs by kind, on purpose. |
| 96 | + |
| 97 | +An issue is closed because nobody is building it, so the message asks for a |
| 98 | +pull request. It deliberately does not offer "leave a comment" as the way to |
| 99 | +revive one: a comment clears the stale label without moving the issue any |
| 100 | +closer to done, which is how a backlog gets kept alive without getting |
| 101 | +shipped. |
| 102 | + |
| 103 | +A pull request is already the contribution, so the message asks for a commit |
| 104 | +instead, and points at `stale:keep` for the case where the hold-up is a review |
| 105 | +on our side rather than the author. |
| 106 | + |
| 107 | +Keep that split if you change the wording. |
| 108 | + |
| 109 | +## Bumping |
| 110 | + |
| 111 | +A comment clears the stale label and buys another full cycle, on issues and on |
| 112 | +pull requests alike. That is upstream behaviour and it is not configurable: |
| 113 | + |
| 114 | +- `remove-stale-when-updated` controls whether an update removes the label, and |
| 115 | + a comment counts as an update. |
| 116 | +- Turning it off does not help. The close gate in `actions/stale` is |
| 117 | + `if (!issueHasCommentsSinceStale && !issueHasUpdateInCloseWindow)`, so any |
| 118 | + human comment after the label blocks closing whatever that option is set to. |
| 119 | +- `ignore-pr-updates` measures from `created_at` instead of `updated_at`, which |
| 120 | + would mark every long lived pull request stale no matter how much work it is |
| 121 | + getting. |
| 122 | + |
| 123 | +So a determined bump wins, and the messages say plainly that it is a limitation |
| 124 | +rather than the intended path: on a pull request the thing that counts is a |
| 125 | +commit, and a bump leaves the work exactly as unfinished as it was. |
| 126 | + |
| 127 | +Making commits the real currency would mean taking the pull request lifecycle |
| 128 | +off upstream and measuring it from the head commit date. That is a bigger |
| 129 | +change than this action wants to be today. |
0 commit comments