← All guides

What does "stale" mean on a GitHub issue, and who decided?

A comment has appeared on your issue saying it has been marked stale because it has had no activity for 60 days, and that it will be closed if nothing happens. Nobody read the issue before that comment was posted.

Stale means one thing. A program woke up on a schedule, subtracted one timestamp from another, and found the gap larger than a number in a configuration file. It has no view on whether the bug is real, whether it was fixed, or whether the report was any good, and no person approved the comment you received.

What the clock is actually counting

The bot reads GitHub's updated_at on the issue, and that field moves on almost any event, not only on somebody speaking. Adding a label moves it. Editing the title moves it. Setting a milestone, changing an assignee, another bot posting a dependency notice: all of them move it. Adding a reaction does not, so a thumbs-up on the issue registers as nothing at all.

So "no activity in 60 days" really means "no event in 60 days", and those are different statements. An issue nobody has thought about since last year can read as perfectly fresh because a housekeeping bot relabelled it a fortnight ago.

The damaging version is the mirror image of that. A maintainer reads your issue, asks you a direct question, and their comment resets updated_at to zero. If the question is never answered, the issue is granted a fresh 60 days, and when that runs out it is closed in exactly the same way as an issue nobody ever opened. The state a maintainer would most want to find again, "we asked and got nothing", is the state this measurement hides best.

Three quantities would be more useful, and not one of them is a field GitHub hands you. A bot would have to walk the issue timeline and work each one out for itself:

  • Time since the last comment of any kind, ignoring labels and milestones.
  • Time since the last comment by a person rather than by another bot.
  • Time since the last comment by somebody other than the party now being waited on.

The third is what people mean by the word when they use it about a human conversation. An issue is stale when whoever owes the next move has not made it.

What happens after the label, in order

The defaults below are those of actions/stale, the action GitHub publishes for this job. Other bots differ, and the workflow file in the repository you are reading is the only authority on that repository, so check there before assuming.

  1. A label is added. The default is Stale, with a capital S. wontfix was the default in the older Probot app, not in this one.
  2. A comment is posted. That is the one you received.
  3. A second and much shorter clock starts: 7 days by default, against 60 for the first.
  4. If nothing happens, the issue is closed with a reason of not_planned rather than completed. That is the same close reason a maintainer chooses when declining something deliberately, which is part of why a timer close reads as a decision.

What resets it. Any comment at all, because remove-stale-when-updated defaults to true. The comment does not have to contain anything. "Still an issue" clears the label exactly as well as a detailed reply does.

The exception. If ignore-updates has been switched on, the calculation runs from when the issue was created instead of from when it was last touched. In that configuration nothing you post resets anything, and commenting will not save the issue.

What exempts it. Exempt labels, exempt assignees and exempt milestones all exist as settings, and all of them are empty or off until somebody fills them in.

The three states a timer cannot tell apart

Sixty days of silence is produced by at least three unrelated situations.

  • Waiting on the maintainer. The report is complete, reproducible and correct. Nobody has had time. Silence here means the queue is long.
  • Waiting on the reporter. A question was asked and never answered. The question may have been too much work to answer rather than the reporter having stopped caring, which is a fixable problem and the subject of the clarifying question that gets an answer.
  • Genuinely abandoned. The reporter moved teams, or upgraded, or worked around it and forgot. Nobody is waiting for anything.

All three produce an identical absence of events, and the bot closes all three with identical wording. The objection to stale bots is not that they are blunt. It is that they measure a quantity which cannot separate the cases anybody cares about, and then act on it.

If your issue was marked stale and you still have the problem

Replying "still an issue" clears the label and changes nothing else. The issue goes back into the same queue, in the same condition, and the same comment arrives again in 60 days.

Put three things in one comment instead:

  • The version you are on now, which is probably not the version you filed against.
  • Whether it still happens on the current release, stated plainly. If the answer is yes, reproduction steps written against that version are the most useful thing you can attach, because they make the issue startable by somebody who has never seen it.
  • The one thing that has changed since you filed: a new workaround, a second affected team, a severity that went up.

If it has stopped happening, say so and close it yourself. "Stopped reproducing on 4.2, closing" is a genuine finding and a far better record than a close by calendar.

If you run the bot, the settings that matter

Set days-before-stale longer than your own time from filed to triaged. If the bot fires in 60 days and your backlog routinely takes longer than that to reach a first read, the bot is closing issues nobody has looked at yet, and it will be indistinguishable from a bot that is working. Time from filed to triaged is worth measuring for its own sake, and this is one of the decisions it should govern.

Exempt labels are the setting worth reaching for first, because they let you protect the waiting-on-us pile. An issue that is complete, correct and simply unstaffed should not be closed for having been filed well and then ignored by you.

Give the asked-and-got-nothing pile a separate and much shorter clock. It goes cold faster than an untouched backlog item does, and it is the one case where a reminder is useful rather than nagging. Our page for maintainers makes the wider case for handling that pile on its own.

And write the closing comment as what it is. A stale close is a filing decision, not an answer. Saying so, and saying that reopening is welcome, costs one sentence and changes what the close reads as.

The cheapest version of this is not running one

WhatProblem asks questions on issues, so it has the same problem in a faster form, and it takes the least dramatic option available. A conversation that has gone quiet for 24 hours is treated as over, rather than 60 days, because a clarifying question has a far shorter useful life than a bug report does.

Two things about it are worth separating from what stale bots do. It is not a timer: nothing sweeps the backlog, and the 24 hours is only ever worked out when the next comment arrives. And nothing on GitHub is touched. No label, no comment, no close. The conversation is marked as stopped on our side and the bot stops talking, which is a decision about our own behaviour rather than about the issue. Anybody can start it again by commenting !whatproblem analyze.

What "stale" does not mean

It does not mean duplicate. It does not mean rejected, and it does not mean won't fix, even when the label says wontfix. It is not a verdict on how the issue was written, and it is certainly not evidence that the bug was fixed, since a fixed bug and a forgotten one leave the same trace.

A stale close is a statement about a calendar. The honest reading is that nobody got to it in time, which is the same distinction that separates a status from a verdict when an issue is closed for not reproducing. If the problem is still real, the timer has not made it less real, and reopening the issue is the correct response.

WhatProblem does this in the issue thread

It reads new GitHub issues and asks what is missing, a minute or two after they are opened, before anyone on your team has to.

Install from GitHub Marketplace