# GitHub issues: bug reports, templates, labels and milestones

> Turn a one-line request into GitHub issues the team can act on: how to write a bug report with steps to reproduce, feature requests with acceptance criteria, a GitHub issue template, labels, assignees, sub-issues and a milestone with a due date. Plus how closing keywords and mentions link pull requests to issues, and what each issue state tells a PM.

- Course: [Git and GitHub for Product People](https://productovi.com/courses/git-for-product.md)
- Module: GitHub for product work
- Lesson 13/15, ~42 min
- Source: https://productovi.com/courses/git-for-product/issues-labels-milestones

## One line from the CEO

**Alex Tutor:** Monday of week three at CatChow, and your product team's loyalty work is spread over chat threads, three pull requests and one issue from the CEO. Where would a new teammate look for it?

**Sofia:** Everywhere, and they'd still miss half. The CEO opened #147 last week. Title: "Loyalty". Description: "Need this before Black Friday!!" That's all of it.

**Alex Tutor:** Then let's give the work a proper home. An **issue** is GitHub's place for anything the team should know about or do: a bug report, a feature request, a task. It usually comes before any code, and today it also gets a template.

**Daniel:** The issue is the question, the pull request is one answer to it. I can open a pull request without an issue, but a month later nobody remembers which problem it solved.

```text
Issues in catchow/website, Monday morning
- [Open] Loyalty #147: Opened by the CEO last week · no labels, no assignee
- [Open] Test an orange checkout button #135: Label idea · opened by Daniel
- [Closed as completed] Checkout button stays grey after editing cart #131: Bug report from support · closed by #134
- [Closed as not planned] Show prices in euros #138: Feature request · closed as not planned
```

**Sofia:** So #131 was a bug report, #138 a feature request we turned down, and #135 an idea waiting for a test.

**Alex Tutor:** And #147 is a feature request that doesn't yet say what the feature is. We'll fix that today. First, learn to tell what deserves an issue and what arrives as a pull request.

> **Exercise:** Match each situation with what it should become on GitHub.

## How to write a bug report anyone can repeat

**Alex Tutor:** Issues are read by people who weren't in the room. A good one says what is wrong or wanted, why it matters, and how we'll know it's done.

**Alex Tutor:** For a feature, that means a title naming the outcome, a few lines of context, **acceptance criteria** (checks that must be true when it's done) and what is out of scope.

**Sofia:** And for a bug? This morning's staff test gave me a perfect one.

**Daniel:** Steps to reproduce, the expected result, the actual result, and where: phone or desktop, which account, which order. If I can't make it happen on my screen, I can't fix it.

**Alex Tutor:** Some context first. After #156 was merged this morning, Daniel switched `loyalty-points` on for staff only. Customers still see nothing.

**Sofia:** An order for 600 UAH with a 100 UAH promo code, so 500 paid. The account showed 60 points. It should be 50: one point per 10 UAH paid.

> **Issue #159, as filed**
>
> **Title:** Points counted before promo discount
>
> ```markdown
> **Steps to reproduce**
> 1. Log in with a staff account (flag loyalty-points is on for staff).
> 2. Add products for 600 UAH to the cart, apply a 100 UAH promo code.
> 3. Pay 500 UAH by card.
> 4. Open Account → Loyalty points.
>
> **Expected:** 50 points (1 point per 10 UAH paid).
> **Actual:** 60 points, counted from 600 UAH before the discount.
>
> **Where:** desktop, Chrome, staff account, order 58213.
> **Screenshot:** the account page showing 60 points.
> ```
> Labels `bug` and `loyalty`, assignee Daniel.

**Alex Tutor:** Anyone can repeat that in two minutes, and the two numbers make the gap obvious. Now compare it with what arrives from support on a busy day.

![A person follows footprints painted on the floor to a shop counter, where two piles of coins of clearly different heights sit side by side.](https://productovi.com/courses/git-for-product/inline/issues-labels-milestones-illustration-2.webp)

*Steps, expected result, actual result: anyone can see the problem in two minutes.*

> **Exercise:** Support files an issue titled "Points broken" with the description "customer says points are wrong, pls fix ASAP". What does Daniel need before he can start? Select all that apply.

**Alex Tutor:** Bug reports have a twin: the feature request. Here are two versions of the same request from sales. Which one could Daniel start on?

> **Exercise:** Sales wants customers to reuse their delivery address. Two versions of the feature request. Which one can the team **act on**?

## GitHub issue templates

**Sofia:** Can GitHub make everyone write issues like that?

**Daniel:** It can nudge them. An **issue template** is a file in the repository's `.github/ISSUE_TEMPLATE/` folder that pre-fills every new issue of one kind with headings and labels. This morning I opened #160 with two of them. Oscar approved it an hour ago, so they're live.

**Alex Tutor:** Now `New issue` first asks which kind: "Bug report", "Feature request" or a blank issue, as of 2026. The bug report template starts with the headings you just saw and adds the `bug` label by itself.

**Daniel:** There's also a form version: separate fields instead of headings, and some fields can be required. I kept ours simple: headings with a one-line hint under each.

**Sofia:** So the template gives the headings. Filling them in well is still up to whoever writes.

**Alex Tutor:** Exactly: a template can require a field, not a good answer. Try it on the next message from the team chat.

> **Exercise:** Emma, CatChow's content marketer, writes in the team chat: "Ordered for 450 UAH on my phone yesterday, staff account, paying by card on delivery. Points still show 0 today. Screenshot attached." Fill in the "Bug report" template: a title, steps to reproduce, expected and actual result, and where. Mark anything you still need to ask.

## GitHub labels and assignees

**Alex Tutor:** Now the sidebar on the right of an issue. It has the fields you met on pull requests, and they answer a PM's everyday questions.

**Alex Tutor:** `Labels` say what kind of work it is and which area: `bug`, `content`, and `loyalty`, the label Daniel made for the programme. Filter the list by `label:loyalty` and the whole programme is on one screen.

**Sofia:** And `Assignees`? On #134 that was Daniel, the author.

**Alex Tutor:** On an issue it's whoever is responsible for moving it forward, not whoever wrote it. #159 was written by you and assigned to Daniel. You can assign several people, but one name makes it clear whom to ask.

**Daniel:** And keep labels few. If every issue carries six labels, filtering by any one of them tells you nothing.

**Alex Tutor:** Two more fields, `Milestone` and `Development`, get their own sections today. First, the whole sidebar at a glance.

> **Exercise:** Match each field in an issue's sidebar with the question it answers.

## Sub-issues: a parent and its pieces

**Sofia:** Back to #147. The programme is far too big for one issue. Should I open ten separate ones?

**Alex Tutor:** Separate, yes, but connected. As of 2026 an issue can have **sub-issues**: ordinary issues attached to a parent. The parent lists them and shows how many are closed, like "1 of 4".

![A big kite flies high with several smaller kites tied along its tail; each small kite's string is held by a different person.](https://productovi.com/courses/git-for-product/inline/issues-labels-milestones-illustration-3.webp)

**Daniel:** Each sub-issue is a full issue with its own assignee, labels and state. I can close mine without touching yours.

**Alex Tutor:** Older repositories do something similar with a checklist in the description: lines starting with `- [ ]` that show up as tick boxes. They work, but someone ticks them by hand.

**Sofia:** So #147 becomes the parent: the goal and what "launched" means. The sub-issues are the pieces people actually pick up.

> **Exercise:** A sub-issue is only a line of text inside the parent issue's description, so it has no state, labels or assignee of its own.

## Rewriting #147

**Alex Tutor:** Time to rewrite #147. The CEO's one line becomes a parent issue people can act on. Keep his point about Black Friday and add everything he left out.

**Daniel:** From my side: #155 and #156 are merged, and #159 is your bug. Still to build: the balance in the account and spending points at checkout.

**Sofia:** And Emma's #157, the programme page, is still a draft. "Launched" means the flag goes on for all customers, and that switch should be a task of its own.

**Alex Tutor:** Then write the parent, list the sub-issues with owners and labels, and decide how the milestone and the pull requests will point at them.

> **Exercise:** Rewrite issue #147 as a parent issue and plan its sub-issues.

## GitHub milestones: a goal with a date

**Alex Tutor:** Here's where the team ended up. #147 is renamed, four sub-issues hang under it, and there's a **milestone**: "Loyalty launch", due 13 November.

**Alex Tutor:** A milestone groups issues and pull requests under one goal, with an optional due date and a progress bar. Labels answer "what kind"; a milestone answers "towards what, and by when".

**Daniel:** I put #155, #156 and #157 in it too. Pull requests can belong to a milestone, and merged ones count as closed.

```text
Milestone "Loyalty launch" · Due by November 13
- [Open] Launch loyalty points before Black Friday #147: Parent · 0 of 4 sub-issues closed · Sofia
- [Merged] Add loyalty points behind a flag #155
- [Merged] Earn points on paid orders #156
- [Draft] Write loyalty programme page #157: Emma
- [Open] Points counted before promo discount #159: bug, loyalty · Daniel
- [Open] Show points balance in the account #162: loyalty · Daniel
- [Open] Spend points at checkout #163: loyalty · Daniel
- [Open] Switch on loyalty points for all customers #164: loyalty · Sofia
```

*GitHub shows "25% complete": 2 closed, 6 open.*

**Sofia:** A quarter done, then?

**Alex Tutor:** A quarter of the *items* are closed. The bar counts items, not effort, and it counts every closed item, whatever the reason. A dropped idea closed as not planned moves it just like finished work.

![A progress bar of four equal blocks has one filled; behind the empty blocks sit boulders of very different sizes.](https://productovi.com/courses/git-for-product/inline/issues-labels-milestones-illustration-4.webp)

*Read the list under the bar, not just the share that's done.*

**Sofia:** So I read the list under the bar, not just the percentage.

> **Exercise:** What does the progress bar of this milestone show?

## How to link a pull request to an issue

**Alex Tutor:** Tuesday morning. Daniel opens #165, the fix for your bug. You met closing keywords on #134; today, look at them from the issue's side, with a parent in the picture.

**Daniel:** I started from the issue itself. Under `Development` in #159 there's `Create a branch`. GitHub suggests a name from the issue's number and title; I changed it to our habit, `fix/points-after-discount`.

```text
Commit graph, oldest first: From issue #159 to main
1. commit e5f81a3 on main "Hide loyalty menu link when flag is off (#158)", tagged v1.4.1
2. commit b2e6f10 on main "Earn points on paid orders (#156)"
3. commit 6e0b4d8 on main "Add issue templates (#160)"
4. branch fix/points-after-discount starts from commit 6e0b4d8
5. commit 4f8c2e1 on fix/points-after-discount "Count points from the paid amount"
6. commit 7a1d9c3: fix/points-after-discount squash-merged into main "Count points after discounts (#165)" (highlighted)
```

*The branch was created from the issue, so #159 lists it under Development. When #165 is squash-merged, its commit on main closes #159.*

**Alex Tutor:** The description of #165 has two references that look alike and behave differently.

> **The "Why" part of #165**
>
> ```markdown
> ## Why
> Fixes #159. Part of #147.
> Points were counted from the order total before promo codes.
> ```

**Sofia:** `Fixes #159` closes the bug when #165 is merged into `main`. And `Part of #147` is only a mention?

**Alex Tutor:** Yes. A plain `#147` creates a cross-reference: #147's timeline shows that #165 mentioned it, and nothing closes. Only close, fix or resolve in front of the number closes it, and only after a merge into the default branch.

**Daniel:** Which is exactly why I never write `Fixes #147` in a slice. The first merged piece would close the whole programme.

**Alex Tutor:** One more trap: every number needs its own keyword. "Fixes #159 and #162" closes only #159. Write "Fixes #159, fixes #162".

![Fixes #159 in pull request #165 closes the bug when it is merged into main; Part of #147 only mentions the parent, which stays open.](https://productovi.com/courses/git-for-product/diagrams/m5-l1-closing-keywords.png)

> **Exercise:** Daniel's next pull request, #166, says in its description: "Fixes #162. Related to #163 and #147." It is approved and squash-merged into `main`. Which issues does GitHub close?

**Alex Tutor:** Now the trap with several numbers. Daniel's next pull request finishes two sub-issues at once.

> **Exercise:** Daniel's pull request #167 finishes two sub-issues, #162 (balance) and #163 (spending at checkout), and is one more slice of the loyalty programme #147. Which description closes **both sub-issues and nothing else** when it's merged into `main`?

## Closed as completed, and then?

**Sofia:** After the merge, #159 shows the purple icon: closed as completed, automatically.

**Alex Tutor:** And `Development` keeps the link to #165, so anyone can see what closed it and when. If the fix turns out not to work, the issue can be reopened.

**Daniel:** The staff test after lunch passed: 500 UAH paid, 50 points. So the bug is fixed in the live code, though customers still don't see points at all.

**Alex Tutor:** Keep those two apart when you report. The issue state says the work is done; the flag says who can see it.

> **Exercise:** Support asks: "A customer heard about loyalty points. Is the points bug fixed for customers now?" The flag `loyalty-points` is still on for staff only. What is the accurate answer?

**Alex Tutor:** That's the issue side of GitHub: a clear problem, an owner and a few labels, a parent with sub-issues, a milestone with a date, and pull requests that close exactly what they fix.

**Sofia:** The milestone tells me how many items are closed. It doesn't tell me who's working on what right now.

**Daniel:** For that we need a board. Tomorrow I'd like to drag my cards somewhere, not write status messages.

**Alex Tutor:** Next lesson, then: a project board that follows loyalty points from the first issue to the day customers see them.

- Previous lesson: [GitHub releases and versions: tags, SemVer, changelogs](https://productovi.com/courses/git-for-product/releases-and-versions.md)
- Next lesson: [GitHub Projects: project boards from issue to release](https://productovi.com/courses/git-for-product/project-boards.md)

---
Productovi Learning. Free, no sign-up needed to read.
