GUIDE

Bug report template, with a GitHub issue form

A bug report template to copy, five questions to ask a customer who says it’s broken, a GitHub issue form in YAML, and a good report beside a bad one.

By the Deacon team

Published

A useful bug report tells whoever fixes it what broke and how to make it happen again. That takes a title, what the person was trying to do, the steps, what they expected and what happened instead, plus the page, the browser, the time and any error message. The template below has those fields, ready to paste into GitHub, Linear, Jira or a doc. Most reports from customers arrive as “it’s broken” and nothing else, so this page also gives you five questions any customer can answer, where to find the rest yourself, and the same template as a GitHub issue form.

Deacon is an AI customer support agent for founders without a support team. It answers your customers in seconds from your own docs. When someone tells its chat that something’s broken, it keeps the page they were on and lists their report beside the JavaScript errors your site threw. There’s a free plan, and it needs no card.

A bug report template to copy

Paste this into an issue, a ticket or a doc and fill in the brackets. Delete a line you can’t fill rather than guess at it, because a wrong browser or a wrong page sends you looking in the wrong place.

Title: [What broke and where, in one line, e.g. "CSV export does nothing on the Reports page"]

Reported by: [name or email, and their plan if it matters]
When: [date, time and time zone]
Page: [full URL]
Browser and device: [e.g. Safari on an iPhone, Chrome on Windows]
Release: [the version, build or commit that was live]

What they were trying to do:
[one sentence, in their words]

Steps to reproduce:
1. [Start from a state anyone can set up, e.g. "Log in as a Pro user"]
2. [Each click or entry, one per step]
3. [The step where it goes wrong]

What should have happened:
[what the person expected, or what your docs promise]

What happened instead:
[what they saw, with any error message copied word for word]

How often: [every time / sometimes / once]
Who's affected: [one customer / everyone on one browser / everyone]
Severity: [blocks them / has a workaround / looks wrong but works]
Workaround: [anything that gets them past it for now, or "none found"]

Evidence:
[screenshots, a screen recording, console errors, log lines, a link to the chat or email]

Five questions for a customer who says “it’s broken”

A customer can’t tell you their browser version or write steps to reproduce, and asking in those words usually gets no reply. Ask about what they did and what they saw instead. These five questions get you most of a bug report in words anyone can answer.

Sorry about that, and thanks for telling me. Could you help me track it down?

1. What were you trying to do when it went wrong?
2. What did you click or type just before?
3. What did you expect to happen, and what happened instead?
4. Could you send a screenshot of your whole screen, with the address bar showing?
5. If you try once more now, does it happen again?

Even one or two answers help, and I'll let you know when it's fixed.

The first question gets you their goal, which often names the page and tells you whether it’s a bug at all or a screen that didn’t make sense. The second gets you the last step before it broke, which is usually the step that set it off. The third separates a fault from a misunderstanding, since what they expected may not be what the product does.

You learn the most from the screenshot. A picture of the whole window shows the address bar, so you get the page, and it shows any error on screen in its exact words. You can usually tell the browser and whether they’re on a phone from it too. The retry tells you whether it’s stuck or a one-off, and if it works the second time, that points at something passing, such as a slow request or a deploy that landed while they were on the page.

Don’t ask a customer for their console, a HAR file or their browser version unless they’re a developer. You can find most of that yourself, as the next section shows. When the fix ships, tell them. Our customer service email templates have one for owning up to a bug and one for checking the fix worked.

What each field is for, and where to find it yourself

You can fill in most of the template from your own data, so a report with three answers from the customer is enough to start on.

Title

Why you need it
You’ll scan it in a list, so it should name what broke and where.
Where to find it when the reporter can’t say
Write it yourself once you’ve read the report.

What they were trying to do

Why you need it
It tells you if you’re looking at a bug, a missing feature or a confusing screen.
Where to find it when the reporter can’t say
Ask. It’s the one question every customer can answer.

Page

Why you need it
It narrows the search to one screen and one part of your code.
Where to find it when the reporter can’t say
The address bar in their screenshot, or the page your chat tool recorded with their message.

Steps to reproduce

Why you need it
They let you, or a test, watch it happen.
Where to find it when the reporter can’t say
The pages their visit went through in your analytics, then try the same path yourself.

What should have happened

Why you need it
It separates a fault from a misunderstanding.
Where to find it when the reporter can’t say
Ask, then check what your docs and your pricing page promise.

Error message

Why you need it
Exact words are searchable in your code, your logs and your error tracker.
Where to find it when the reporter can’t say
Their screenshot, your error tracker, or the browser console if they can open it.

Browser and device

Why you need it
Some front-end bugs show up in one browser only, which rules out most of your code.
Where to find it when the reporter can’t say
The user agent in your server logs or your error tracker.

When

Why you need it
It lets you match the report to log lines and error events.
Where to find it when the reporter can’t say
The timestamp on their message. Note the time zone.

Reported by

Why you need it
You need their account to look at their data, plan and settings.
Where to find it when the reporter can’t say
Their email in your own database, or the logged-in user your app passed to the chat.

Release

Why you need it
It tells you which deploy to look at first.
Where to find it when the reporter can’t say
Lay the time against your deploy log or git log.

Who’s affected

Why you need it
You use it to decide what to fix first.
Where to find it when the reporter can’t say
Count the reports of the same thing, and the people your error tracker says each error hit.

Severity

Why you need it
It’s how badly the bug hurts the person who hit it, and you weigh it when you set priority.
Where to find it when the reporter can’t say
Ask whether they could get the job done another way.

A good bug report and a bad one

This is the bug report you’ll usually get. Sam and the details below are made up for the example.

Export is broken again. I need this report for a meeting at 3, please fix it ASAP.

Sam’s message is the start of a report, and this is what it looks like once you’ve filled it in.

Title: CSV export does nothing on the Reports page in Safari

Reported by: Sam, sam@example.com (Pro plan), in the chat on our site
When: 10 November 2026, 14:05 UTC
Page: https://app.example.com/reports?range=30d
Browser and device: Safari on a Mac
Release: v2.14.0, deployed 9 November 2026

What they were trying to do:
Download last month's report as a CSV for a meeting.

Steps to reproduce:
1. Log in as a Pro user and open Reports.
2. Set the range to Last 30 days.
3. Press Export, then CSV.

What should have happened:
A file called report-2026-10.csv downloads.

What happened instead:
Nothing. The button greys out for a second and comes back. Our error tracker
logged "TypeError: undefined is not an object (evaluating 'blob.stream')" at 14:05.

How often: Every time in Safari. Works in Chrome and Firefox.
Who's affected: 4 customers since v2.14.0, all on Safari.
Severity: Blocks them.
Workaround: Use Chrome.

Evidence: Sam's screenshot, the logged error above, a link to the chat.

The filled-in report is quicker to fix for five reasons.

  • The title names what broke, where and in which browser, so you’d spot a duplicate in a list of 50 issues.
  • The steps start from a state anyone can set up, a Pro account and a 30-day range, rather than from Sam’s account.
  • The error is copied exactly, so you can search your code and your error tracker for it.
  • “Works in Chrome and Firefox” rules out most of the code before anyone opens it.
  • The time and the release point you at the log lines from that afternoon and the commits that shipped the day before.

Sam supplied the goal, the screenshot and the fact that it happens every time. The page and the time came from the chat, the browser and the error from the error tracker, the release from git log and the count from three other reports of the same thing. Most reports split this way, because a customer can tell you what they wanted and what they saw, and you have to find the rest yourself.

The bug report template as a GitHub issue form

GitHub turns a YAML file into a form that people fill in when they open an issue. Save the file below as .github/ISSUE_TEMPLATE/bug-report.yml on your default branch, and Bug report appears in the template chooser when anyone opens a new issue. We read GitHub’s docs on issue forms and its form schema on 10 November 2026. Both say the feature is in public preview and subject to change.

name: Bug report
description: Something in the app is broken or behaves wrongly.
title: "[Bug]: "
labels: ["bug"]
body:
  - type: markdown
    attributes:
      value: |
        One bug per issue. If a customer reported it, paste their words as they wrote them, and link the chat or email rather than copying their address.
  - type: input
    id: page
    attributes:
      label: Page
      description: The full URL where it happened.
      placeholder: https://app.example.com/reports
    validations:
      required: true
  - type: textarea
    id: goal
    attributes:
      label: What they were trying to do
    validations:
      required: true
  - type: textarea
    id: steps
    attributes:
      label: Steps to reproduce
      placeholder: |
        1. Log in as ...
        2. Open ...
        3. Press ...
    validations:
      required: true
  - type: textarea
    id: expected
    attributes:
      label: What should have happened
    validations:
      required: true
  - type: textarea
    id: actual
    attributes:
      label: What happened instead
      description: Copy any error message word for word.
    validations:
      required: true
  - type: dropdown
    id: frequency
    attributes:
      label: How often
      options:
        - Every time
        - Sometimes
        - Once
    validations:
      required: true
  - type: dropdown
    id: browsers
    attributes:
      label: Browsers
      multiple: true
      options:
        - Chrome
        - Safari
        - Firefox
        - Edge
        - Other or not known
  - type: dropdown
    id: severity
    attributes:
      label: Severity
      options:
        - Blocks them, no workaround
        - Has a workaround
        - Looks wrong but works
    validations:
      required: true
  - type: input
    id: when
    attributes:
      label: When it happened
      description: Date, time and time zone, so you can find it in the logs.
      placeholder: 10 November 2026, 14:05 UTC
  - type: input
    id: release
    attributes:
      label: Release
      description: The version, build or commit that was live.
  - type: textarea
    id: logs
    attributes:
      label: Errors and logs
      description: Console errors, stack traces or log lines. They're formatted as code for you.
      render: shell
  - type: upload
    id: screenshots
    attributes:
      label: Screenshots or a recording
    validations:
      required: false

A few things in GitHub’s docs are worth knowing before you rely on the form.

  • required: true stops someone submitting with the field empty only in a public repository. The form schema says “Only for public repositories” against every element that takes it, so in a private repository the form asks for each field but can’t insist.
  • A label in labels has to exist in the repository already. If it doesn’t, GitHub doesn’t add it to the issue.
  • render: shell posts the box’s text as a code block, and the box then won’t expand for file attachments. That’s why screenshots get their own upload field, which takes images up to 10 MB and videos up to 100 MB.
  • The name has to be more than 3 characters, or the form isn’t shown when someone creates an issue.
  • To stop people skipping the form, add .github/ISSUE_TEMPLATE/config.yml holding blank_issues_enabled: false. Anyone with write access still sees a blank issue, labelled Maintainers only, GitHub says.

Each id also works as a URL parameter, so a link can open the form half filled in. https://github.com/OWNER/REPO/issues/new?template=bug-report.yml&page=https%3A%2F%2Fapp.example.com%2Freports opens it with the page already entered. GitHub’s page on creating an issue from a URL query covers the other parameters, such as title and labels. A link like that, built in your admin panel from a customer’s chat, saves retyping the page.

Try Deacon for free

Install Deacon

The same template in Linear, Jira and GitLab

The plain template pastes into any tracker as it is. Each of these three can also keep it in the tracker itself, so nobody has to go looking for it. We read each one’s own docs on 10 November 2026.

In Linear, add it under Workspace settings, then Templates, or Team settings, then Templates. A teammate picks it with the shortcut Option/Alt C, or by pressing Template next to the team name in a new issue. Linear’s form templates turn the fields into a form where any field can be required, and they also work in Linear’s Slack integration and in Asks.

Atlassian’s support article for Jira Cloud adds the template with an automation rule. The trigger is Work Item created, the condition is a Work Item fields condition with Issue Type equal to Bug, and the action is Edit Work Item, setting Description to {{issue.description}} with your template after it. So the template lands once the bug has been created, below whatever the reporter typed. To show it while someone is still filling in the form, the article points to a third-party app.

On GitLab, save the template as a Markdown file in .gitlab/issue_templates/ on your default branch, such as Bug.md, and it shows in the Choose a template list on a new issue. Call the file Default.md instead, and GitLab’s docs say every new issue is pre-filled with it, on the Free tier as well as the paid ones.

A support ticket template for the customer’s side

A bug report is about the fault, and a support ticket is about the person waiting on you. One bug can bring in ten tickets, so keep them apart and link each ticket to its issue. These are the fields worth having on each ticket, in a ticketing system or a spreadsheet, and free ticketing systems compares the tools you can start on without paying.

Ticket: [number]
Customer: [name, email, plan]
Came in by: [chat, email, phone]
Opened: [date and time]
Their words: [first message, unedited]
Type: [bug, question, billing, feature ask]
Linked bug: [issue link, if it's a bug]
Status: [new, waiting on us, waiting on them, solved]
Reply owed by: [date and time]
Told when fixed: [yes or no]

Keep Told when fixed. On the day the fix ships, the tickets linked to that issue are your list of people to tell, and sorting them during triage is easier when Type is filled in from the start.

How Deacon fills in what the customer leaves out

The slow part of most bug reports comes before you read any code, while you go back and forth with the customer for the page, the time and the error. Deacon keeps much of that with the chat where they reported it, and puts the errors your site logged beside it.

A customer tells the chat on your site that something’s broken the way they’d ask any question, with no form to fill in. The conversation keeps the page they were on and the time they wrote. A screenshot they add stays with the conversation for you to open, and Deacon reads it too, copying out any error message on it. When your app passes Deacon the name and email of whoever’s logged in, you see who reported it, and Deacon.setContext() can pass their plan as well. Where your docs cover the problem, such as a known bug with a workaround, Deacon answers with it in seconds. Where they don’t, it says it doesn’t know rather than guess, and offers a field for their email so you can reply.

The same script logs the JavaScript errors your own pages throw, so you can look the error up instead of asking for it. Errors on your pages, on Analytics › Website, shows which browsers each one hit, and a tag such as Only in Safari answers the browser line for you. Website visitor tracking has more on the errors and the visits they hit.

Topics put reports of the same bug together and count the people behind them, a starting point for your Who’s affected line. On Analytics › Widget, What needs you lists those bug reports by topic beside your site’s errors, each with a strip of the days it hit, so you can see when it started. Each bug topic there has Copy this bug report for your coding agent, which copies the customers’ own words as Markdown for Claude Code or Cursor.

Open the issue from your own code

On Integrations, add your URL as a webhook and pick the event for a visitor reporting a bug. Deacon then sends the visitor.reported_bug event to your server as soon as it decides a question is reporting something broken, once per chat. The event carries the customer’s words, the page, the time and a link to the chat in your dashboard, and there are seven other events to pick from. Your code turns it into an issue. GitHub’s endpoint for creating an issue takes a title, a body and labels but no template, so your code writes the template’s headings into the body itself.

// Runs once you've checked the webhook's signature, as the API docs show.
async function openIssue(event) {
  if (event.type !== 'visitor.reported_bug') return;

  const { conversation, message } = event.data;
  const said = message.untrusted.text;

  const body = [
    '### What the customer said',
    said.split('\n').map((line) => `> ${line}`).join('\n'),
    '### Page',
    message.untrusted.page ?? 'Not recorded',
    '### When',
    message.created_at,
    '### The chat',
    conversation.dashboard_url,
    '### Steps to reproduce',
    'To fill in',
  ].join('\n\n');

  await fetch('https://api.github.com/repos/OWNER/REPO/issues', {
    method: 'POST',
    headers: {
      Accept: 'application/vnd.github+json',
      Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
    },
    body: JSON.stringify({
      title: `From chat: ${said.slice(0, 70)}`,
      body,
      labels: ['bug'],
    }),
  });
}

The customer’s words go in as a quote, because they’re a stranger’s text, and everything a stranger wrote sits under untrusted in the event so your code knows to treat it as data. The issue links the chat rather than copying the customer’s email, which matters if the repository is public. A webhook can arrive twice, so keep each event.id you’ve handled and skip a repeat. The GitHub token needs push access to the repository, or GitHub drops the labels without saying so. The API docs show how to check the signature.

Let your coding agent read the report

Connect Claude Code, Codex, Cursor or VS Code to Deacon’s MCP server, and your agent can read the report itself. It finds it with search_conversations, reads the chat with get_conversation and your site’s errors with list_site_errors, then checks them against your code. Paste this into your agent to have it fill in the template.

Find the newest bug report in Deacon. Read the chat, and the JavaScript errors our site logged on the same page around that time. Fill in our bug report template from them, and use git log to name the release it most likely came in with. Leave a field blank rather than guess. Then open it as a GitHub issue with gh issue create.

Your agent opens the issue with your GitHub access, and Deacon never touches your code. To run this on every report, have your webhook handler start the agent, for example in a GitHub Action, with a Coding agent key from Use a key instead on Integrations. Keep your agent’s approval prompts on, because a customer can type anything, including instructions meant for your agent.

Webhooks, the MCP server and the API are on every plan, the free one included, and the free plan needs no card. The pricing page has the rest.

Questions about bug report templates

What’s the difference between severity and priority?

Severity is how badly the bug hurts the person who hit it, from a typo to lost work. Priority is when you’ll fix it, weighing severity against how many people it hits and who they are. A crash on a settings page nobody opens can wait behind a slow checkout that every customer meets. Let the reporter set severity, and set priority yourself.

What if I can’t reproduce the bug?

Write down what you tried, on which account and in which browser, and keep the issue open with a can’t-reproduce label. Then look at what’s different about the customer, such as their plan, their data, their browser or the release that was live when they wrote. Search your logs for their account’s requests around that time, and ask for a short screen recording, which shows the steps they didn’t think to mention. If only one account hits it, look at that account’s data before your code.

Should customers file bugs on GitHub themselves?

Rarely. They’d need a GitHub account, and for a private repository you’d have to give them access to it. In a public one, anyone can read what they post, screenshots included. Let customers report in your chat or by email, in their own words, and open the issue yourself or have your code do it. Keep the issue form for your team, and for the developers who use an open source project.

Does Deacon open the GitHub issue for me?

No. Deacon has no GitHub, Linear or Jira integration. When a customer reports a bug, the webhook tells your own code and your code opens the issue, as in the example above. Or your coding agent reads the report through the MCP server and opens the issue itself.

Does Deacon catch every bug report?

No. A model reads each question and decides whether it’s reporting something broken, so it can miss one, and the webhook comes once per chat. The script doesn’t record an error thrown before it loads, or one on a page where you’ve switched page views off. Deacon also can’t tell whether a customer’s report and a logged error are the same fault, so it shows them side by side for you to judge. Every chat is still in Conversations.

Try it on your own documentation

Add your help pages, ask it the question you know your docs cannot answer, and watch it say so.

The free plan covers 50 answers a month on one seat, and the paid plans are on the pricing page.

Start free

Free plan, no card.

READ NEXT