StackPractices
beginner By Mathias Paulenko

Bug Report Template (Markdown + Examples)

A structured bug report template to help teams reproduce, triage, and resolve defects faster — with a filled example and a GitHub issue form variant.

Use this template when a bug has to move from “someone saw something weird” to a ticket an engineer can act on without a follow-up meeting.

Overview

A bug report has one job: give the person fixing it everything needed to reproduce the issue in a single read. Reports that skip steps or environment details bounce back as “needs more info” and sit untouched for weeks. The structure below exists to kill that round trip.

The template collects eight pieces of information:

  1. Summary — one sentence: what breaks, where, under what condition
  2. Reproduction steps — numbered, isolated, starting from a known state
  3. Expected vs actual behavior — defines what “fixed” means
  4. Environment — OS, browser, app version, deployment environment
  5. Introduced in — the first version where the bug appears, if known
  6. Severity — a standardized impact assessment for triage
  7. Evidence — screenshots, logs, and request IDs
  8. Context — frequency, related work, and recent changes

When to Use

  • Internal trackers — any bug that lands in GitHub Issues, Jira, Linear, or GitLab benefits from the same field set.
  • QA to engineering handoffs — testers already gather steps and environment data; the template makes sure nothing gets dropped on the way into the backlog.
  • Open-source projects — external reporters don’t know your conventions, so the template is how you ask for reproduction details without a comment thread.
  • Support escalations — customer support translates “the export is broken” into something actionable; the template is their checklist.

When not to use it:

  • Security vulnerabilities — never put exploit reproduction steps in a public tracker; use the private disclosure channel described under Variants.
  • Active incidents — if production is down, run the incident process first. The bug report comes later, usually as an output of the incident postmortem.
  • Feature requests — new behavior that was never designed isn’t a bug. Use a feature request template instead.

Template

# Bug Report

## Summary
One-sentence description: what breaks, where, and under what condition.

## Steps to Reproduce
1. Go to '...'
2. Click on '...'
3. Observe the error

## Expected Behavior
What should have happened.

## Actual Behavior
What happened instead. Quote the exact error text.

## Environment
- **OS:** [e.g., macOS 14, Windows 11]
- **Browser:** [e.g., Chrome 126, Safari 17]
- **App Version:** [e.g., v2.4.1]
- **Environment:** [staging / production / local]

## Introduced In
First version or commit where the bug appears, if known. "Worked in v2.3.x, broken in v2.4.0" is enough — it tells the engineer where to look.

## Severity
- [ ] Critical — data loss, security breach, complete outage
- [ ] High — major feature broken, workaround difficult
- [ ] Medium — feature degraded, workaround exists
- [ ] Low — cosmetic issue, minor inconvenience

## Evidence
- Screenshot or screen recording:
- Console error, stack trace, or failed network call (request ID):
- Relevant logs:

## Additional Context
- Frequency (always / intermittent / once)
- Links to related issues, PRs, or [feature requests](/docs/feature-request-template/)
- Recent changes that may be related

Writing a Good Title

The title is the one line that survives in search results, triage queues, and Slack threads. The formula: what breaks + where + under what condition.

Bad:  "Export doesn't work"
Good: "CSV export returns an empty file when the date range crosses a month"

Bad:  "Login broken"
Good: "SSO login redirects to /error after successful authentication (Okta only)"

The good versions are also searchable — the next person with the same bug finds them by the error text instead of filing a duplicate.

Filled Example

# Bug Report: CSV export downloads an empty file when the date range crosses a month

## Summary
Exporting analytics data as CSV produces an empty file when the selected range
crosses a month boundary (e.g., March 28 to April 3).

## Steps to Reproduce
1. Navigate to /analytics/dashboard
2. Select the date range 2026-03-28 to 2026-04-03
3. Click "Export CSV" on the "Revenue by Day" chart
4. Open the downloaded file

## Expected Behavior
The CSV contains 7 data rows (one per day) with the columns date, revenue, orders.

## Actual Behavior
The CSV downloads with headers only and zero data rows. No error appears in the
UI or in the browser console.

## Environment
- **OS:** macOS 14.4
- **Browser:** Chrome 126.0
- **App Version:** v2.4.1
- **Environment:** production

## Introduced In
Worked in v2.3.9, broken since the v2.4.0 deploy. The date-range refactor in
PR #412 is the likely culprit.

## Severity
- [x] High — export broken for a common use case, no workaround

## Evidence
- Server log: the query returns 0 rows for `WHERE date >= '2026-03-28' AND date <= '2026-04-03'` — points to a date-parsing bug
- Request ID: req-9f2a-81c3 (searchable in Kibana)

## Additional Context
- Reproduces whenever the range crosses any month boundary; single-month ranges work
- Regression candidate — the range picker shipped in v2.4.0
- Writing a regression test for this fits the flow in [Test-Driven Development](/guides/test-driven-development-guide/)

Why This Structure Works

SectionPurpose
SummaryThe reader decides priority in five seconds
Reproduction stepsThe engineer can confirm the bug locally
Expected vs actualDefines what “fixed” means
EnvironmentBugs are often platform- or data-specific
Introduced inPoints at the change that likely caused it
SeverityOrders the triage queue
EvidenceSaves the “can you attach the logs?” round trip
Additional contextLinks related work and root-cause clues

Severity vs Priority

Severity measures impact; priority decides the order of work. They often diverge, and mixing them up is how triage meetings stall:

  • A typo on the login page is low severity but high priority — every user sees it.
  • A crash that only affects an end-of-life OS is high severity but may wait a sprint.

Reporters propose severity because they know the impact. Triagers assign priority because they see the whole queue. If your tracker has a priority field, hide it from reporters — people always pick the top one.

A rule of thumb that holds up in practice: severity is a property of the bug, priority is a property of the roadmap. Re-triage when either one changes.

Severity Definitions

SeverityCriteriaResponse timeExample
CriticalData loss, security breach, complete outageImmediate (< 1 hour)All API requests returning 500
HighMajor feature broken, no tolerable workaroundSame day (< 8 hours)Export produces an empty file
MediumFeature degraded, workaround existsThis sprint (< 1 week)Sort direction reversed on one column
LowCosmetic issue, minor inconvenienceNext sprintButton color doesn’t match the design

Bug Lifecycle

Most trackers use the same transitions under different names — Jira has “To Do / In Progress / Done”, GitHub does it with labels. The flow is the same:

flowchart diagram: subgraph Intake
  1. Reported — the reporter fills out the template and creates a ticket
  2. Triaged — a reviewer labels it within 24 hours (bug, frontend, needs-repro, wont-fix)
  3. Reproduced — an engineer confirms the bug exists locally or in staging
  4. Assigned — an engineer takes ownership and starts investigating
  5. In progress — root cause identified, fix in progress
  6. PR opened — fix submitted with a regression test; the PR template should reference the bug ID
  7. Verified — the reporter or QA confirms the fix resolves the issue
  8. Closed — ticket closed with the fix version and noted in the release notes

Tips for Reporters

  • Reproduce it twice before reporting — transient issues need different handling
  • Isolate the steps — remove unrelated actions from the reproduction path
  • Test in incognito/private mode — rules out browser extensions
  • Check existing issues first — duplicates waste triage time; search by error text, not by title
  • Include screenshots or screen recordings — visual bugs are hard to describe
  • Attach relevant logs — browser console, server logs, network tab
  • Note the frequency — “always happens” vs “1 in 10 times” changes the priority
  • Mention recent changes — “started after the v2.4.0 deploy” narrows the search

Tips for Triage Teams

  • Label immediately — bug, frontend, backend, needs-reproduction
  • Assign within 24 hours — unassigned bugs rot
  • Request missing info promptly — use the template as the checklist
  • Close stale reports — no activity in 90 days means close it with a note
  • Link duplicates — merge reports of the same issue instead of leaving parallel threads
  • Verify severity — reporters tend to over-rate it; adjust during triage and say why

Bug Report Example (Tracker Format)

The same fields, flattened into a plain-text ticket — the format Jira or email threads end up in:

=== Bug Report: 500 error on order export ===

Title: 500 error when exporting more than 1000 orders to CSV
ID: BUG-1234
Reported by: bob@company.com
Date: 2026-07-12
Severity: High
Environment: Production
Introduced in: v2.4.0

Expected Behavior:
  Clicking "Export CSV" on the orders page with a date filter
  returning more than 1000 orders downloads a CSV with all rows.

Actual Behavior:
  The system returns a 500 error after ~30 seconds.
  The log shows "Database timeout."

Steps to Reproduce:
  1. Log in as administrator
  2. Go to /admin/orders
  3. Select the date range 2026-06-01 to 2026-07-01
  4. Click "Export CSV"
  5. Wait ~30 seconds
  6. Observe the 500 error in the browser

Evidence:
  - Screenshot of the error (attached)
  - Stack trace: https://pastebin.company.com/abc123
  - Request ID: req-abc-456 (searchable in Kibana)

Frequency:
  - Always reproducible with > 1000 orders
  - Works correctly with < 500 orders
  - Intermittent between 500 and 1000 orders

Impact:
  - Sales team can't export monthly orders
  - Temporary workaround: export per week (4 exports)
  - 15 users affected

Environment:
  - Browser: Chrome 126.0
  - OS: macOS 14.5
  - App version: v2.4.3
  - Region: us-east-1

Additional Notes:
  - Endpoint is GET /api/orders/export
  - DB timeout is 30s; the query takes ~45s with 1000+ orders

Variants

One template doesn’t fit every reporter. The four variants below cover the usual adjustments: enforced structure in GitHub, a stripped-down form for customers, machine-generated crash reports, and a private channel for security issues.

GitHub Issue Form (YAML)

GitHub can enforce this template natively. Drop the file below into .github/ISSUE_TEMPLATE/bug-report.yml and reporters get a structured form instead of a blank textarea:

name: Bug Report
description: File a bug report.
title: "[Bug]: "
labels: ["bug", "triage"]
body:
  - type: textarea
    id: summary
    attributes:
      label: What happened?
      description: One or two sentences. What did you expect instead?
    validations:
      required: true
  - type: textarea
    id: repro
    attributes:
      label: Steps to reproduce
      description: Numbered steps from a known starting state.
      placeholder: |
        1. Go to '...'
        2. Click on '...'
        3. Observe the error
    validations:
      required: true
  - type: dropdown
    id: version
    attributes:
      label: Version
      description: First version where you saw the bug.
      options:
        - v2.4.x
        - v2.3.x
        - older
    validations:
      required: true
  - type: dropdown
    id: severity
    attributes:
      label: Severity
      options:
        - Critical
        - High
        - Medium
        - Low
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Logs and evidence
      description: Console errors, stack traces, request IDs. Remove credentials and personal data first.
      render: shell

validations.required is the part that pays off — a report can’t be submitted without reproduction steps. Full field reference in the GitHub issue forms documentation.

Customer-facing (support portal)

Customers shouldn’t see severity labels or internal fields. Use a simplified form — Summary, What were you trying to do?, What happened instead?, Screenshot upload — then map submissions to internal bug reports during triage.

Crash report (automated)

Automated crash reports from error tracking (Sentry, Bugsnag, Crashlytics) populate the template themselves: stack trace, device info, app version, user actions before the crash, and crash frequency. See the error handling recipe for structured error reporting.

Security vulnerability report

Security reports need a different template: reproduction steps don’t belong in a public ticket. Use a private disclosure channel and include the affected component, attack vector, potential impact, and suggested mitigation. The security audit checklist covers the surrounding process.

Adapting the Template to Your Tracker

TrackerMechanismWhere the template livesField enforcement
GitHub IssuesIssue forms (YAML).github/ISSUE_TEMPLATE/validations.required per field
GitLab IssuesDescription templates.gitlab/issue_templates/Bug.mdBy convention — no schema enforcement
JiraBug issue type + screen schemeProject settingsRequired fields in the field config
LinearIssue templatesTeam settingsPartial — prefilled title and body

Whatever the tracker, keep the five core fields — summary, steps, expected, actual, environment. If the tool can’t enforce them, put the checklist inside the template body so the reporter sees it while typing.

When the process changes — a new severity level, a renamed state — update every template in the same pull request. Trackers drift apart fast: the GitHub form asks for a field the Jira screen doesn’t have, and six months later half your reports arrive without an environment. Treat the template as a living artifact, versioned next to the code it describes.

Measuring Report Quality

If you own the triage process, a few numbers tell you whether the template is working:

  • Time to reproduction — days between “reported” and “reproduced”. If it stretches, reports are missing steps or environment details.
  • Bounce rate — the share of reports sent back as needs-info. Anything above ~20% means the template asks for the wrong things or hides required fields.
  • Duplicate rate — high duplication means the bug is either very visible (raise its priority) or the tracker search is failing.
  • Verification rate — fixes confirmed by the reporter. Fixes nobody verifies tend to come back as reopened tickets.

Common Mistakes

  • Vague title — “export broken” forces a follow-up question; “CSV export empty when the range crosses a month” doesn’t.
  • Several bugs in one report — each bug needs its own owner, severity, and fix. Bundle them and the easy one gets fixed while the ticket closes with the hard one still open.
  • Severity inflation — when everything is Critical, triage stops trusting the label.
  • Steps without a starting state — “click Export” assumes the reader knows you were on /admin/orders with a June filter. Start from a logged-out or fresh state.
  • No environment details — most “works on my machine” threads are a missing environment field. OS, browser, and version take 30 seconds to add.
  • Screenshots with secrets — auth tokens in headers, customer emails, internal hostnames. Crop or redact before uploading.
  • Duplicates filed by title — reporters search for their own phrasing, not the error text. Search by the error message or endpoint.
  • Fixing without a regression test — the fix should reproduce the bug first, or it returns in a different form. Integration testing covers how to verify across environments.

Frequently Asked Questions

What if I can't reproduce the bug consistently?

File the report anyway. Mark the frequency as "intermittent" and include timestamps, logs, and any pattern you've noticed — "only after 10 minutes of inactivity" is a real clue.

Should customers use this template too?

Yes, but simplified. Customers get a form with Summary, Steps, and Environment only; internal teams get the full template with severity and context fields.

How do I handle "works on my machine" bugs?

Add environment parity checks: Docker, exact dependency versions, seeded test data. If it only happens in production, the bug is in the data or the configuration, not the code.

What's the difference between a bug and a feature request?

A bug is behavior that doesn't match the documented or intended behavior. A feature request asks for behavior that was never designed. If it's ambiguous, check the spec or ask the product owner, then use the feature request template for new capabilities.

What information is mandatory in a bug report?

Mandatory: a descriptive title, steps to reproduce, expected vs actual behavior, environment, and frequency. Recommended: screenshots, stack traces, request IDs, user impact, and any workaround. A report without reproduction steps can't be confirmed — if you can't reproduce it, say so and give context.

How do we determine the severity of a bug?

Severity measures business impact, not technical complexity. Critical means service down or data loss (resolve in 24h); High means a main feature is broken for many users (48h); Medium means a workaround exists (this sprint); Low means minor impact (30 days). Ask: how many users, how often, is there a workaround, does it hit revenue? Severity can drop after triage — document why when it changes.

How do we handle duplicate bug reports?

Search the tracker by error keywords before creating a report — not by title, since reporters phrase things differently. If a report exists, add your findings as a comment instead of filing a duplicate. If you're unsure, file it marked "possible duplicate of #XXX". Five duplicates of the same bug is a signal to raise its priority.

What if the bug is in a third-party dependency?

File it in your internal tracker for visibility, and file upstream in the dependency's issue tracker with a link between both tickets. Note the dependency version and whether a workaround exists — and if the dependency is abandoned, say so and start planning a migration.