Writing Good Issues & Using Labels
Every piece of work at the studio starts as a GitHub issue: a bug, a feature, a chore. A good issue lets anyone, a teammate, a new hire, or an AI agent, understand the problem and start work without a meeting. This guide covers how to write one, how we label them, and how they connect to pull requests.
Step 1: Check Before You File
- Search first. The problem may already be reported. If it is, add your details as a comment rather than opening a duplicate.
- One problem per issue. "Save crash and also the tooltip is wrong" becomes two issues, because they'll be fixed, reviewed, and closed separately.
- Make sure it belongs in an issue. Questions go to the team channel; security vulnerabilities go privately to your lead, never in a public issue.
gh issue list --search "save crash" --state all
Step 2: Write Bug Reports Someone Can Reproduce
The goal of a bug report is to let someone else see the bug for themselves. A bug that can't be reproduced usually can't be fixed. Include:
- A specific title: what happens and where. "Game crashes when saving to a read-only folder", not "Saving broken".
- Numbered steps to reproduce, starting from a known state.
- Expected and actual results.
- Environment: build or version, platform and OS, device, browser, and the type of tenant or account (never a real customer's name or data).
- How often it happens: every time, sometimes, once.
- Evidence: error messages pasted as text, logs, screenshots, or a recording, with personal data removed.
## Summary
The game crashes when saving if the save folder is read-only.
## Steps to reproduce
1. Start a new game on Windows 11.
2. Make %USERPROFILE%\AppData\LocalLow\ShatteredVales\Saves read-only.
3. Press Esc > Save.
## Expected
An error message: "Couldn't save. Check folder permissions."
## Actual
The game freezes for ~2 seconds and closes. No error shown.
## Environment
Build 0.8.3 (Steam), Windows 11 23H2
## Frequency
Every time.
## Logs
UnauthorizedAccessException: Access to the path '...\slot1.sav' is denied.
at SaveSystem.WriteSlot (System.Int32 slot) [0x0004a] in SaveSystem.cs:88
Step 3: Write Features as User Stories with Acceptance Criteria
For new features, describe the need, not just the solution. A user story says who wants something and why; acceptance criteria say exactly how everyone will know it's done. Business analysts usually own this part; developers should push back when criteria are vague.
## User story
As a tenant admin, I want to set how many days after the due date an
invoice becomes overdue, so that reminders match our payment terms.
## Acceptance criteria
- [ ] Given I am a tenant admin, when I open Billing Settings, then I can
set "Overdue after" to a whole number of days from 0 to 90.
- [ ] Given the threshold is 15 days, when an invoice is unpaid 15 days
after its due date, then its status shows "Overdue".
- [ ] Given I am not an admin, when I open Billing Settings, then the
field is read-only.
## Out of scope
- Per-customer thresholds.
- Changing the reminder email wording.
## Notes
Default for existing tenants: 30 days (current behavior).
The Given / When / Then format makes every criterion testable. For issues an AI agent will work on, also see Writing Issues for Agentic AI.
Step 4: Apply the Standard Labels
Labels make issues searchable and drive the team's boards. Every issue gets one type label; leads add priority during triage.
| Group | Labels | Meaning |
|---|---|---|
| Type | bug · feature · chore · docs | What kind of work it is. Exactly one per issue. |
| Priority | P0 · P1 · P2 · P3 | P0: drop everything (outage, data loss). P1: this sprint. P2: soon. P3: nice to have. |
| Status | needs-triage · blocked · needs-info | Waiting on a lead, another task, or the reporter. |
| Area | for example area:billing, area:inventory | Part of the product. Defined per repository. |
| Workflow | good first issue | Small, well-defined, a good start for new team members. |
| Workflow | agentic | Ready for an AI agent. Applied only by maintainers. |
Repository admins can create missing labels from the terminal:
gh label list
gh label create "needs-triage" --color FBCA04 --description "Waiting for a lead to review"
Step 5: Link Issues and Pull Requests
Mentioning #42 anywhere in a pull request, commit, or comment creates a link that appears on issue #42's timeline. Pull request descriptions use Resolves #42, which also shows the PR in the issue's "Development" section.
GitHub only closes issues automatically when a pull request with a closing keyword merges into the repository's default branch. Because our pull requests merge into Dev, issues stay open after merge. Close them yourself once your pull request is merged, with a comment linking it, unless your team closes them at release.
- Sub-issues break a large feature into pieces that each become one pull request.
- Assign yourself before starting, so nobody else picks up the same work.
- Keep the conversation on the issue. Decisions made in chat or meetings should be summarized in a comment, so the issue stays the source of truth.
Step 6: Add Issue Templates to Each Repository
Templates in .github/ISSUE_TEMPLATE/ appear as choices when anyone clicks New issue, with the right sections and labels already filled in. Each template is a Markdown file with a short header:
---
name: Bug report
about: Something isn't working as expected
title: ""
labels: ["bug", "needs-triage"]
---
## Summary
## Steps to reproduce
1.
## Expected
## Actual
## Environment
## Frequency
## Logs / screenshots
<!-- Remove personal and customer data first. -->
Save as .github/ISSUE_TEMPLATE/bug_report.md. Add feature_request.md with the user story format from Step 3.
Step 7: Manage Issues from the Terminal
gh issue list --assignee @me # your open issues
gh issue list --label "good first issue" # a place to start
gh issue view 42 # read an issue (add --web to open it in the browser)
gh issue create --title "..." --label bug --body-file bug.md
gh issue edit 42 --add-assignee @me # claim it
gh issue comment 42 --body "Reproduced on build 0.8.3."
gh issue close 42 --comment "Fixed in #57."