How I Use GitHub Issues To Organize a Project

I am a huge Github user, I use it for all my projects, and over the past year, I’ve started to use GitHub Issues as the primary technique to organize the project, and I’ve generally enjoyed it, but it took some playing around to come up with a process.

I wanted to share this process with you for your projects.

GitHub Issues has a slim set of features:

This isn’t a lot to work with, so I had to play around a bit to figure out a good pattern.


I decided there was only a couple things I actually wanted to track:

  1. MVP - what tasks are needed to launch our MVP?
  2. Next Steps?
  3. What aren’t we sure about?

I have to regularly reevaluate where issues fit into these categories, so I broke category 2 into:

2a. Stuff to probably do soon

2b. Stuff to probably not do soon

I actually tried using labels for this but it was no good. There’s only a small number of queries you can do with labels, foiling any clever ideas. Instead we have milestones:

MVP: this is the “main” milestone. You write down what you are trying to accomplish with the milestone. You then create a new milestone when you are ready for the next iteration.

Next Steps: this is a standing “Next Tasks” milestone. I never change or rename this milestone.

Stuff to probably do soon: this is a standing “Blue Sky” milestone. I like to refer to these tickets and sometimes look through them, but they are easy to ignore, somewhat intentionally ignored.

What aren’t we sure about?: issues with no milestone.

I also use a permanent “Next Steps” milestone (as opposed to renaming it to “Alpha 3” or actual-next-iteration milestone) because I don’t want to presume or default to including something in the real next iteration. When I’m ready to start planning the next iteration I’ll create a new milestone, and only deliberately move things into that milestone.

The Triage Meeting

I like to use the triage process to organize many of my meetings. The issues give us the first run at an agenda.

Needs Discussion”

I have a label: needs discussion. I start each meeting by querying everything (regardless of milestone) that has that label, and discussing each item. Once I’m done discussing I usually remove the label, but sometimes I couldn’t decide whatever I wanted to decide and so I’d leave the label there, pushing the item to the next meeting.

It’s helpful to use “needs discussion” liberally. It might be for some big things, like I’ll add a ticket “Decide who our target market is” — that’s kind of a big deal, and I might keep adding notes over the course of weeks. But it might just be a small issue where there seems to be some confusion or an open question.

These are small team meetings, so we aren’t trying to use this to close off discussion or restrict the agenda, and anyone can bring up a topic at any time. But if you want to be sure to talk about something then opening and labeling a ticket is as good a way as anything. Also the issues become our meeting notes (they aren’t date-organized meeting notes, but I seldom want date-organized meeting notes).

Issues without a milestone

GitHub lets you query all issues that have no milestone. It doesn’t let you query issues without a particular label, which made our label-based attempts at organization unproductive.

So next in the meeting we go through each item without a milestone, oldest first. Sometimes we skim, and if someone says “next tasks?” then usually the answer is “yes”.

Outside of a meeting if any of us sees something in Next Tasks or Blue Sky that we should do sooner, that person clears the milestone. It’s often better to clear the milestone than to just assign it to the current iteration, as it brings it in front of the entire team.

An exception is when someone breaks a big issue down into smaller tasks, or creates a dependent bug — then whoever does that usually assigns the milestone at the same time.

Starting a new iteration

When we’re ready to start a new iteration we first create a new milestone, and agree on our goal.

Sometimes if we’re not sure what our goal for the next iteration is we’ll create a ticket, “decide on goal for the next iteration” (and mark it needs-discussion of course).

We’ll go through the meeting as normal, then look at any open issues in the previous iteration. It’s pretty common that we move these issues to “Next Tasks” instead of the next iteration. Issues that didn’t get fixed often turned out to not be as important as we thought.

We sometimes go through Next Tasks, but it can also take too long to do that together. Part of me thinks we should slog through anyway, and use that as a chance to clean up Next Tasks — move stuff to Blue Sky, or close issues we no longer plan to address at all, or check for things that have already been fixed.

But instead one of us will often do an initial triage on Next Tasks, clearing the milestone for anything that might be worth looking at again. Also it’s a chance to close issues and find duplicates. It’s good to err on the side of clearing the milestone. It’s best not to assign a task directly to the next iteration because even talking about a ticket for a moment is useful.

Blue Sky unfortunately is a bit of a dumping ground. The only way issues emerge is when we search and happen upon something that was put into Blue Sky. Arguably we shouldn’t create a bucket for things we don’t want to pay attention to. But the issue list is also an idea collection area, and I like that. I’d like if we had a process to recover things from Blue Sky.


Sometimes we assign all the tickets in the current iteration. The primary purpose of assigning all the tickets is to identify the issues that no one is planning to address (and fix that).

Maintaining the tickets

It’s important that tickets have proper titles. Sometimes I have left bad titles in place and it would cause repeated confusion — constantly asking myself and other people, what was this ticket about? GitHub has editable titles and you should use them. Editing the main ticket description for clear mistakes (broken links, s/now/not/, etc) is also useful.

Still issues have a limited lifetime. When the main description of the issue is no longer what you intend to implement it’s time to open a new issue, and close the original with a reference to the new issue. Long comment threads are not useful. Comments should indicate additions and clarifications, but when a comment changes the goal of the issue it’s too easy to skim over. Also when a debate happens in the comments and is resolved, it’s hard to separate the resolution from the debate, and a new ticket fixes that.

Sometimes there’s a collection of work that goes together. We tried two approaches, but neither stuck:

Create a label for the group of issues: in this model I might create a database label and label a bunch of issues. This would help me figure out just how much work was left on that area and what bugs I should keep in mind as I look at a particular area of code, and maybe help find bugs or at least be sure I wasn’t forgetting about something.

It’s easy to think that you should start building a taxonomy of bugs. The default GitHub labels imply something like this (like “bug”, “duplicate”, “enhancement”). I would sometimes find myself using these just because they are there, but always ask: why did I bother? Then I started deleting these labels to remove the temptation. I want only actionable labels.

An aside: the labels duplicate, wontfix, and invalid are (a) unnecessary, and (b) socially dangerous. If something is a duplicate you can close it with the comment “Dup of #123”. Often that’s not exactly what happened though, you might say “rendered moot by #123” or “once #123 is fixed this won’t be an issue” or something specific. And wontfix is the worst, it’s like a way of telling someone to fuck off, but that you don’t even care enough to actually tell them why they should fuck off. If it’s a team member they won’t take offense, but it’s easy to fire it at some unwitting member of the public. Take the time to close the bug with a comment about why you won’t fix it. Invalid is like the vague passive aggressive combination of the other two. Maybe it’s because we like to imagine we’ll do some kind of reporting on these statuses, but I find myself pretty bored by history.

Create a master issue: in this case you create an issue that links to all the sub-issues that need to be tackled.

To do this you can’t just use backlinks (the link created whenever one issue references another): all backlinks look the same, and you can’t tell if a bug that references the master is a dependency of the master, or depends on the master, or just mentions it in passing.

Instead we list all the bugs in the main description of the master issue, and edit as necessary. You can use [ ] to make the bug list a checkable list. Unfortunately issue links don’t get crossed out when they are closed (but they should, that would be a very helpful improvement).

The master ticket works okay but feels like it requires a lot of manual record keeping, and it’s easy for your master issue to get out of sync with the sub-issues. Also as you add or remove items from the list of dependent bugs you have to edit the main ticket description (anything in the comments would be too easy to lose) and there aren’t notifications for those edits.

The result?

I’ve only tried this approach with a small dedicated team. It wouldn’t match the less consistent rhythm of an open source project, and I have no idea how it would work with a larger team (maybe okay?)

One attribute is notably and deliberately missing: priorities, severities, and time estimates. We played with these and I never knew what to do with them. There’s no single equation that determines what you should work on, and trying to create such an equation seems pointless — you probably won’t include everything you need to include, and if you do then it’s unlikely that all parameters are filled in accurately and so you can’t trust the results.

The goals for me, and the team, is to keep things somewhat organized, to remember important things, and to come to a shared understanding of what we’re trying to accomplish and how. Because of that last point some parts of the process deliberately force conversation where a tighter process wouldn’t need it. Specifically I don’t want important things expressed through labels because there’s no opportunity to discuss labels, they just appear and disappear. Issue trackers that encourage complex taxonomies of bugs make me worry that the taxonomy will be used to assert things about process that need to be communicated directly and explicitly and via a two-way channel.

GitHub’s permissive approach to editing (titles, descriptions, and comments) is reflective of the kind of process I also want to support: everyone should be able to do everything in the service of the project. It’s better to give people more power: it’s actually helpful if people can overreach because it is an opportunity to establish where the limits really are and what purpose those limits have. Most tools have a strict append-only approach which I do not like.