If a Workflow Has No Cheap Way to Say No, It Will Say Yes

Four bug tickets moved in one day and no code was written. One travelled the full development path because a developer read a field name correctly; another was closed into a status named Ready For Sit Deployment with nothing to deploy.

Four bug tickets moved in one day. Not a single line of code was written for any of them.

Every one of those movements is now sitting in the transition history, indistinguishable from real work.

What the Four Days Looked Like

The first of the four went the whole way. Ready for Development, then Developing, then Waiting for Review, then Waiting For Test.

The reason it moved: a developer read the field name in the report, saw that the field name was correct, and sent the ticket back. That is the entire story. No branch, no commit, no review of anything — but the ticket could not get back to the tester without passing through the states that exist to describe a fix, so it passed through them.

Another of the four was closed with the comment "this is working fine". The status it landed in was literally named Ready For Sit Deployment.

There was nothing to deploy. The ticket was in a queue for a release step that had no artefact to carry.

The Mechanism

The workflow has no state that means this is not a defect.

That is it. That is the whole cause, and everything else follows from it mechanically.

Look at what a developer actually needs when they disagree with a report. They need a way to hand the ticket back to the tester with a sentence attached. In this workflow the only exit that returns a ticket to the tester is the one that also means "fixed, go retest".

So the disagreement gets expressed in the only vocabulary available. The developer picks the transition that moves the ticket in the right direction, and the workflow attaches a meaning to that transition that the developer never intended and cannot suppress.

Nobody is misusing the tool here. They are using the only route that exists.

Identical Footprints

Now look at the same events from the data side, because this is where it stops being an annoyance and starts being a measurement problem.

A misread field name and a real regression leave identical footprints.

Both tickets pass Ready for Development → Developing → Waiting for Review → Waiting For Test. Both produce timestamps on each hop. Both increment whatever counter is watching that path. If your escape metric, your defect-density figure or your cycle-time chart is built on status transitions — and most are, because transitions are the only thing every ticket reliably has — then the two are the same event.

The consequence is worth stating plainly: every escape metric built on status transitions is counting corrections of the tester as defects that escaped.

Not inflating them slightly. Counting them as the real thing, at full weight, with a complete audit trail behind them that looks exactly like the audit trail of a genuine fix.

And the direction of the error is consistent. There is no compensating category — nothing in the workflow quietly under-counts real defects to balance it out. The bias only runs one way.

The Rule

A workflow needs a cheap, one-hop way to say no. Cheap meaning: the person who disagrees can express the disagreement without moving the ticket through any state that implies work was done.

Cheap is the operative word, not possible. Most workflows technically permit a rejection — through a resolution field buried three clicks deep, through an admin transition, through a status nobody has the permission for. If saying no takes more steps than saying yes, people will say yes and correct it in a comment. Comments are not queryable in the way statuses are, so the correction never reaches the metric.

Design the disagreement path to be the shortest path, and the data cleans itself up as a side effect.

How To Actually Do It

  • Add a terminal state that means "not a defect", and make it reachable in one hop from triage. It needs a reason on it — misread specification, environment issue, working as designed, duplicate — because "invalid" as a single bucket tells you nothing about which of those keeps happening.
  • Route rejection back to the reporter without passing through any development status. If the only way home runs through Developing, the workflow is generating the false data, not the people using it.
  • Exclude the "not a defect" path from every metric that describes the product, and put it in a metric that describes the process. Those tickets are not noise. They are the clearest record you will get of where the spec and the tester's model of the system diverged.
  • Audit your closed statuses for ones that promise an artefact. Anything named after a deployment step is a promise that something will ship. If tickets with nothing to ship can land there, that queue is no longer trustworthy as a release input.

Why This Escapes Everyone

Because each individual instance is a five-minute irritation and the damage is entirely aggregate.

A developer who clicks through four statuses to return one ticket has lost nothing worth complaining about. A tester who receives it back and reads the comment understands immediately what happened. The conversation resolves. The people involved consider the matter closed, and they are right to — at the level of a single ticket, nothing went wrong.

The cost lands somewhere none of them will ever look: in a chart, weeks later, read by someone who was not in that conversation and has no way to distinguish the four tickets that moved that day from four that were actually broken.

That is the shape of it. The individual event is harmless and self-correcting; the record it leaves is permanent and wrong. Which is why the fix belongs in the workflow configuration rather than in anybody's behaviour.

In your tracker, how many clicks does it take to say "this is not a defect" — and does that path go through a status named after writing code?