How to write a bug report
- steps
- 8
- named failure modes
- 5
- definition-of-done criteria
- 6
How to write a bug report, in one paragraph
A bug is a defect in something already shipped: live behaviour that contradicts what was approved. It is not a missed requirement, and it is not code you dislike. Every bug hangs off the quarter's Bug Budget epic, so opening one still traces up to a priced outcome. A good bug ticket reproduces on someone else's machine, states expected against actual with the approved source linked, carries a severity set against a published rubric and priced per week, and closes on a regression test that failed before the fix and passes after it.
That is the procedure. The call is where it meets your delivery structure.
Talk it throughWhat these are. The delivery operating model our engagements install alongside client teams: the method underneath the agentic work rather than the agentic work itself, published in full and free to use. It is written for the person running a quarter, not for a buyer, so if you are evaluating us, read the five priced engagements or the case studies instead.
Raise a bug when live behaviour contradicts something that was approved and shipped. If nobody ever agreed the behaviour being demanded, you are writing a story or an epic. If no user can see the problem, you are writing tech debt.
- Time to run it once
- About ninety minutes, to raise and triage it
- What you need
- The delivery tracker, and the quarter's Bug Budget epic
- The environment the defect was seen in
- Logs, a trace ID and a screen recording of the failure
If you do not have those in place, the call is a good place to work out what comes first.
Talk it throughOn this page
Step by step
Each step is deep-linkable, so you can send a colleague the one that is in dispute.
- 1
Classify it before you write a word
Three destinations, one question each.
Did someone approve this behaviour and did we ship something different? Bug, under the quarter's Bug Budget epic. Did nobody ever agree it? New scope: a story under an existing epic, or a new epic if it carries its own value. Is the defect invisible to users and painful only to engineers? Tech debt, under the quarter's Tech Debt epic. Read the source before you decide: the acceptance criteria on the story, or the user journeys and test requirements on the outcome ticket in an AI-native team. Where the source is silent, it is scope, not a bug.
- 2
Reproduce it three times, then record the path
Write steps a stranger can follow without asking you anything: environment, build or release version, account type and permissions, data state, the exact input, and the timestamp of an occurrence you watched happen. Run it three times and record the hit rate, three of three or one of five, because intermittency changes both the fix and the test. Attach a request or trace ID, the log lines either side of the failure, and a short screen recording. If you cannot reproduce it, say so in the first line rather than leaving it implied, and list what you tried, how many users hit it per day, and where you stopped.
- 3
State expected against actual, cite the source
Two sentences, then a title.
Expected: the behaviour that was approved, quoting the acceptance criterion, user journey or test requirement it comes from, with a link. Actual: what happens instead, in the same terms. Citing the source is what stops triage becoming an argument about whether anyone agreed the behaviour. Title in the shape area, what breaks, for whom, under what condition. "Checkout: card payments over £500 fail for guest users with a generic error" beats "payments broken". Keep your theory of the cause out of the title and put it in the body, labelled as a theory.
- 4
Set severity by impact, then price it
Publish a rubric and hold to it.
S1: a key journey cannot be completed, no workaround exists, or there is revenue, safety or regulatory exposure. S2: a key journey is degraded and support can talk a user through a workaround. S3: everything else, including cosmetic defects and edge cases with negligible reach. Then price it: affected users per week times the value lost per affected user, using the conversion and basket numbers the parent outcome was priced with. A weekly cost in currency makes the bug rankable against features. One named person may downgrade a severity, and the reason goes in the ticket.
- 5
Link it to the budget and its origin
Attach every bug to the current roadmap's Bug Budget epic, so the chain from defect to priced outcome stays intact.
Then add the link most teams skip: the epic, story or release that introduced it, traced from the release that first shipped the broken behaviour. Keep an "introduced by" field and leave it empty rather than guessing. That backlink turns the budget from a bucket into three numbers at quarter close: burn against forecast, defects per epic, and the severity mix quarter on quarter. Without it you have a count of bugs, which measures how busy you were.
- 6
Make the rollback call explicitly
Anything found inside the seven-day live monitoring window gets a continue, watch or roll back call from a named person, on the record, rather than by drift. Compare two numbers you can write down: exposure per day in currency, and hours to a fix you would trust. Roll back when exposure per day exceeds what the epic earns per day and the trusted fix is more than a day out, or when you cannot yet bound the blast radius. Record the call, both numbers and the reasoning in the bug, including a decision not to roll back, because the root cause analysis will ask.
- 7
Size it and schedule it against the budget
Bugs go through refinement and sizing like everything else, and through the same phases: todo, in progress, in review, done.
Set the budget from your own data, taking the capacity defects consumed over the last three quarters as a percentage of throughput and reserving that much. It is a forecast, not a permission slip. S1s interrupt the sprint. S2s are scheduled inside the quarter. S3s compete on value with everything else, and some should be closed unfixed with a note saying so. Past half the budget before half the quarter, raise it at the next monthly health check while there is still quarter left to react in.
- 8
Fix with a failing test, close with evidence
Write the regression test first and watch it fail on the affected build.
A fix with no test that failed beforehand is a claim, and it is why the same defect returns two releases later. Name the test and the failing build in the ticket so the next person can rerun it. Close when the test passes, a human has reviewed the change, product has approved it, and the behaviour has been demonstrated in the environment where it was reported, not only in staging. Book the root cause analysis for any S1 before you close, and investigate the system that let it through, not the person who wrote the line.
Bring a real piece of work to the call and we will walk it through these.
Talk it throughA checkout defect, priced and routed
Northbound, an invented online retailer, runs a Q3 roadmap against the outcome "Lift checkout conversion from 2.1% to 2.4%", target £1.2m. One epic under it, "Guest express payments", carries a planned £280k and released on day 31 of the quarter. Three days into live monitoring, support flags that guest users paying by card on baskets over £500 get a generic error. It reproduces three times out of three: guest session, card payment, basket £512, build 26.7.3. The ticket reads "Checkout: card payments over £500 fail for guest users with a generic error", links the acceptance criterion that approved the £500 path, and prices the exposure: about 30 failed attempts a day at an average £610 basket, of which roughly half recover by signing in or retrying, so £9k a day. The rubric says S2 because a workaround exists, but guests hit it silently and never contact support, so it goes S1 on revenue exposure with the reason recorded. Trusted fix is two days against £9k a day, so the release is rolled back the same afternoon, named and reasoned in the ticket. The fix ships with a regression test that failed on 26.7.3, and the bug carries an "introduced by" link to the guest payments epic, which is what the quarter close report will read.
Yours will look different. Thirty minutes is enough to see how.
Talk it throughWhere this goes wrong
- 01
Reclassifying bugs as stories so the budget looks healthy.
It happens quietly, usually under pressure near quarter close, and it makes the burn rate worthless: the number stops describing quality and starts describing how motivated the team was to protect it.
- 02
Severity inflation.
When everything is an S1, nothing is, and the on-call rota becomes a lottery. It happens because severity gets set by whoever is most annoyed rather than against a published rubric, so the fix is the rubric plus one named person allowed to downgrade.
- 03
The unreproducible bug that ages for a quarter.
Nobody closes it because nobody can prove it has gone, so it sits radiating uncertainty. Give it a decision date fourteen days out: reproduce it, ship instrumentation to catch it next time, or close it with the reasoning written down.
- 04
Prompting the fix straight into the code in an AI-native team.
The symptom disappears and the requirement corpus stays untouched, so the next full re-evaluation rebuilds the defect. The corrected journey and the failing test requirement belong in the outcome ticket first.
- 05
Closing on the model's word.
A model reporting that it has fixed something is not evidence that it has, any more than it is evidence when it reports a feature is finished. The passing regression test and the demonstrated journey are the evidence.
Done means
- Reproduction steps are written with a hit rate recorded, and someone other than the reporter has followed them successfully, or the ticket states plainly that it is not reproducible and lists what was tried.
- Expected and actual behaviour are both stated, with a link to the acceptance criterion, journey or test requirement that made the expected behaviour agreed.
- Severity is set against the published rubric, the user impact is named, and a weekly cost in currency is estimated for anything above S3.
- The bug is linked to the quarter's Bug Budget epic and, where identifiable, to the epic, story or release that introduced it.
- A named regression test failed on the affected build and passes after the fix, and the behaviour has been demonstrated in the environment where it was reported.
- The continue, watch or roll back call is recorded against a named person with the two numbers behind it, and any S1 has a root cause analysis booked before the bug closes.
If you recognise one of those already happening, that is a good call to have.
Talk it throughIn an AI-native team the bug is measured against the user journeys and test requirements written on the outcome ticket, since there are no stories or acceptance criteria to cite. That makes the fix a two-part job: correct the journey or add the missing test requirement in the ticket first, then have the model implement against it. Prompting a patch straight into the code leaves the requirement corpus wrong, so the next re-evaluation reintroduces the defect. Closing evidence is the same in both modes and unusually load-bearing here: the regression test passing and the journey demonstrated, never the model's report that it is done.
The two delivery modes, side by side →Which mode your team is actually in is the first thing we establish on a call.
Talk it throughWhere this sits in a programme
The procedure is the same whatever you are building. These cover what it runs into when the thing being built is agentic.
If you want this run inside a programme rather than read, that is the conversation.
Talk it throughMore on writing the work
These guides are written to be read in order.
04How to write a risk or issue in a RAID log
A risk might hurt delivery. An issue is hurting delivery right now. The RAID log is where both live.
How to write a user story
A good story is small enough to build in a sprint, specific enough to test, and honest about the assumptions it carries.
How to write a chapter (a sub-task of a story)
A chapter is what a developer creates when a story turns out to be bigger mid-build. A tactical sub-task, not a user-visible slice.
Or skip ahead and ask which of these your team needs first.
Talk it throughWant help installing this?
These guides are free and you owe us nothing for using them. If you would rather have operators install the operating model alongside your teams and stay until it sticks, that is what our engagements do.
most start with a fixed-price AI Readiness Audit · £44,000 · 4 weeks · working prototypes
Calendar not loading? Open it on cal.com or email hello@tenhaw.com.
Questions
Is a missed requirement a bug?
No. If nobody approved the behaviour being asked for, nothing is defective, the product is doing what was agreed. That is new scope, either a story under an existing epic or a new epic carrying its own share of an outcome's value. The test is whether you can link to an acceptance criterion, user journey or test requirement that the live behaviour contradicts. If you cannot produce that link, you are raising a change request wearing a bug's clothes, and the bug budget will lie about quality all quarter.
Do defects found before release count against the bug budget?
No. A story in progress or in review that does not meet its acceptance criteria is not done, so send it back rather than opening a bug. The same applies in an AI-native team when a test requirement in the outcome ticket fails before release. The bug budget forecasts what escapes into production, and polluting it with in-flight rework destroys the one signal it carries. Track pre-release rework, if you want it, as its own measure of how well work is being written and reviewed.
Who sets severity, and can it be changed?
The reporter sets an opening severity against the published rubric, because they have seen the impact. One named person, usually the product manager who owns the parent outcome, is allowed to change it, and the reason goes in the ticket. Set the rubric on user and revenue impact, never on how loudly the request arrived. Severity decides three things: whether the bug interrupts the current sprint, whether it triggers a rollback conversation, and whether it earns a root cause analysis, so an inflated S1 costs real capacity.
What do we do when the bug budget runs out mid-quarter?
Raise it at the next monthly health check and treat it as a forecast that has broken, not a cap you have breached. Tenhaw reports its own months on those terms, and a month that moves no number the client agreed is written up as a failed month. S1s still interrupt the sprint, because a budget does not make revenue exposure acceptable. What changes is the planned work. Something in the quarter gives way, and product decides which epic slips rather than letting the team absorb it silently. Then look at where the defects came from using the introduced-by links, because an overrun concentrated in one epic is a different problem from one spread evenly.
What makes a good bug report?
One a stranger can act on without finding you. It reproduces on someone else's machine, with the environment, build version, account type, data state and exact input written down. It states expected against actual in two sentences, linking the acceptance criterion or approved journey the live behaviour contradicts, which is what stops triage becoming an argument about whether anyone agreed the behaviour. Severity is set against a published rubric, and anything serious carries a weekly cost in currency. The title names the area, what breaks, for whom and under what condition, so "Checkout: card payments over £500 fail for guest users" beats "payments broken". Keep your theory of the cause in the body, labelled as a theory.
How do you decide between rolling back and fixing forward?
Compare two numbers you can write down, the exposure per day in currency and the hours to a fix you would actually trust. Roll back when the daily exposure exceeds what the change earns per day and the trusted fix is more than a day out, or when you cannot yet bound the blast radius. The call is made explicitly by a named person, on the record, not by drift. On Tenhaw engagements that person is James Rooney, who leads every engagement personally and stays the named escalation route throughout. Record the decision in the ticket with both numbers and the reasoning, including a decision not to roll back, because the root cause analysis will ask for it.
What should we do with a bug nobody can reproduce?
Say so in the first line rather than leaving it implied, then list what you tried, how many users hit it each day, and where you stopped. Attach whatever evidence exists: a trace ID, the log lines either side of the failure, a screen recording. Then give it a decision date fourteen days out with three ways to resolve: reproduce it, ship instrumentation that will catch it next time, or close it with the reasoning written down. The failure mode to avoid is the unreproducible bug that ages for a quarter because nobody can prove it has gone, sitting in the backlog radiating uncertainty.
How do you put a cost on a bug?
Multiply the affected users per week by the value lost per affected user, using the same conversion and basket numbers the parent work was priced with, so both sides of the ledger match. A checkout defect hitting 30 guest payments a day at an average £610 basket, where roughly half recover by retrying, costs about £9k a day. A weekly figure in currency does two jobs. It makes the bug rankable against feature work in planning, and it gives the rollback decision a number to weigh against the hours to a trusted fix. Without it, priority gets set by whoever is most annoyed. Tenhaw reports its own engagements as a measured movement, with delivery lead times at Globelynx falling 60% within six months.
Should you fix every bug?
No. Fix by severity and by value. A defect that blocks a key journey with no workaround, or carries revenue, safety or regulatory exposure, interrupts the sprint immediately. A degraded journey with a workaround gets scheduled inside the quarter. Everything else, cosmetic defects and edge cases with negligible reach, competes on value with the rest of the backlog, and some of it should be closed unfixed with a note saying so. Closing a low-value bug deliberately, with the reasoning recorded, is healthier than letting it age in the backlog pretending it will ever be scheduled.
How do you stop the same bug coming back?
Write the regression test before the fix and watch it fail on the affected build. Tenhaw closes defects on that failing-then-passing test, against an open-source standard of 72 rules with RFC 2119 severities. A fix with no test that failed beforehand is a claim, and it is why the same defect returns two releases later. Name the test and the failing build so the next person can rerun it, and close only when the behaviour is demonstrated in the environment where it was reported, not staging. When a model wrote it, correct the requirement first, the user journey or test requirement on the ticket, then have the model implement against it; patching the symptom leaves the requirement wrong, and the next rebuild reintroduces the defect.
How long should it take to raise and triage a bug?
About ninety minutes, covering both the writing and the triage. That pass classifies it, reproduces it three times, states expected against actual with the approved source linked, sets a severity and a weekly cost, links it to the quarter's bug budget and to the release that introduced it, and records the continue, watch or roll back call. Against dropping a message into a channel that sounds heavy, and it is meant to. The time comes back twice. Nobody re-argues at triage whether the behaviour was ever agreed, and the tickets never bounce back to you for the detail you left out.
How many times should you reproduce a bug before raising it?
Three, and record the hit rate rather than just the fact that it happened. Intermittency changes both the fix and the test that has to prove it, so three of three and one of five are different defects, and an unstated hit rate hides the hardest part of the work. Note the timestamp of an occurrence you watched happen, and attach a request or trace ID, the log lines either side of the failure, and a short screen recording. Then have someone other than the reporter follow the steps successfully before the ticket moves, which is the only real test of whether they were written for a stranger.
When is a defect tech debt rather than a bug?
When no user can see it and it is painful only to engineers. Tenhaw made that split pay at Greggs, where throughput data proved that paying down tech debt made delivery faster. A defect users experience goes under the quarter's Bug Budget epic, and one that only slows the team down goes under the Tech Debt epic. Keeping the line clean matters because the bug budget is meant to describe what customers actually met in production, so filing debt against it makes the burn rate say something it does not mean, and filing a customer-visible defect as debt hides it from the quality signal entirely. Decide before you write a word, because everything downstream trusts the classification.
How do you track which release introduced a bug?
Keep an "introduced by" field on every bug, traced from the release that first shipped the broken behaviour, and leave it empty rather than guessing at it. It is the link most teams skip, and it is what turns the bug budget from a bucket into three numbers at quarter close: burn against forecast, defects per epic, and the severity mix quarter on quarter. Without it you have a count of bugs, which measures how busy the team was rather than where quality is leaking. The quarter close report reads that field, so a bug raised without it goes missing exactly when the pattern matters most.
Who has to sign off before a bug can be closed?
Four conditions, rather than one person's opinion. Tenhaw holds the same four inside client repositories, where the code and documentation are the client's from day one. The named regression test passes, having failed first on the affected build. A human has reviewed the change. Product has approved it. And the behaviour has been demonstrated in the environment where the defect was reported, not only in staging, because staging is not where the report came from. Anything set as S1 also has its root cause analysis booked before the bug closes, and that session investigates the system that let the defect through rather than the person who wrote the line. A model reporting that it has fixed something is not evidence that it has.
How do you stop teams relabelling bugs to protect the numbers?
Make the classification a test anyone can apply, and apply it at triage rather than at quarter close. Tenhaw publishes that test in full and free to adopt, so a delivery team can be held to a public document rather than a private convention. A bug has to cite the acceptance criterion, journey or test requirement that the live behaviour contradicts, so moving one to a story is a claim that no such citation exists, which is checkable, rather than a quiet edit that makes a number look better. It matters because relabelling happens under pressure near the end of a quarter, and it leaves the burn rate worthless. The number stops describing quality and starts describing how motivated the team was to protect it.
What do you check a bug against when there are no acceptance criteria?
The user journeys and test requirements written on the outcome ticket. An AI-native team writes no stories and no acceptance criteria, so those are the approved source, and a bug is live behaviour that contradicts one of them. Cite it by link on the expected line exactly as you would an acceptance criterion, because the citation is what stops triage becoming an argument about whether anyone ever agreed the behaviour. Where the ticket is silent on the behaviour being demanded, you are not looking at a defect at all, you are looking at scope, and it belongs in the ticket as a new requirement.
Do bug tickets need refining and sizing like user stories?
Yes. Bugs go through refinement and sizing like everything else, and through the same phases: todo, in progress, in review, done. The reason is capacity. Unsized bug work still consumes the team's week, it just consumes it somewhere the plan cannot see, and a budget tracked in ticket counts rather than in sized work tells you how busy people were rather than how much of the quarter has gone. Sizing also puts a defect and a feature in the same units, which makes the choice between them a planning decision instead of an improvisation. Past half the budget before half the quarter, raise it at the next health check.