Jon Moshier / Notes / QA Handoff Workflows budding
Note · From the Notebook

QA Handoff Workflows

How engineering teams move a code-complete ticket to a QA analyst for sign-off, and why most use a status transition rather than reassigning the ticket away from the developer.

QA Handoff Workflows

A developer finishes a ticket, thinks it is done, and wants a QA analyst to verify it before it ships. The question that follows is mechanical but consequential: how does the work change hands? The common instinct is to reassign the ticket to the QA person. The durable advice in tracker communities is to do the opposite: keep the developer as assignee and move the ticket through a workflow state instead. The reason is that assignee and status answer two different questions, and collapsing them throws away information.

Assignee and status answer different questions

Assignee answers “who owns getting this to done, and who do I ping if it stalls.” Status answers “what stage is this work in right now.” These are independent fields in most trackers, and treating them as independent is the design choice that makes handoffs legible.

The reassign instinct has a real pull: on a board, the assignee is the fastest glance-read of who is physically holding the work right now, and reassigning makes that read correct for the QA phase. The cost is that it overwrites the answer to the first question. Once the ticket says Bobby, you have lost the record of who wrote the code, and Bobby now nominally owns something he cannot fix. When QA finds a defect, the ticket has to find its way back to the original developer, which is a second handoff the tracker no longer remembers how to make.

Some older trackers make this worse. In Trac’s and Jira’s basic configurable workflows, changing the owner can reset the ticket status to open, so a reassign to QA silently knocks the issue back to the start of the workflow. The accepted fix in those communities is explicit: keep assignee and status as separate, independently-set fields so a tester can pick up a ticket without reverting it from “Ready for Testing” back to “New.” Linear already keeps the two independent, which is why the status-driven pattern is the natural fit there.

The recommendation: leave the developer as assignee through the entire lifecycle. Communicate the handoff with a status change. If you genuinely need a named QA owner, add a separate custom field (“QA Owner”) rather than overloading the assignee.

What the states look like

Linear ships an opinionated default: Backlog, Todo, In Progress, In Review, Done, Canceled, grouped into Backlog / Unstarted / Started / Completed / Canceled. The Started group can hold several states, and Linear’s own team runs In Progress, In Review, and Ready to Merge inside it. Adding QA means inserting states into that Started band, not building a parallel system.

A typical expanded flow, drawn from Kanban testing boards: In Progress → In Review → Ready for QA → In QA → QA Approved → Done. “Ready for QA” is a queue the analyst pulls from. “In QA” means Bobby is actively testing. “QA Approved” is the sign-off state, the thing the original question was reaching for. It is a workflow status, not a person and not a label scrawled in a comment.

Two design decisions sit inside this:

This is the same dynamic described in Writing Code vs. Shipping Code: AI Productivity Across Tool Generations: a 100,000-developer study found AI tools produce large gains in code written but only modest gains in code shipped, because the review and integration stages between “written” and “released” swallow the upstream speedup. The QA gate is exactly one of those stages. Speeding up coding without widening this gate just grows the queue in front of it.

When QA rejects, and why the queue matters

A sign-off workflow is only half a workflow. The other half is what happens when Bobby finds a bug. Two patterns:

The non-obvious risk is the queue itself. When coding speed rises, the “Ready for QA” column fills faster than QA can drain it. A single QA analyst behind a fast team is a classic bottleneck. The standard countermeasure is a [private link] on the QA column: cap it low, and when it fills, developers are forced to stop opening new tickets and instead swarm on reviewing and testing the work already in flight. The limit converts an invisible backlog into a visible stop signal. Without it, the queue grows silently and the amplification resembles a Bullwhip Effect, where a modest increase in developer throughput produces a wild swing in the QA backlog downstream.

The QA handoff also needs a shared [private link]. “QA Approved” only means something if the developer and the analyst agree on what gets checked. Otherwise the gate is a vibe, and tickets pass or fail on whoever happened to test them.

Try it

Model the workflow in Linear and watch a ticket flow (1-2 hours, Linear free tier). Create a team, then in workflow settings add three Started-group states: Ready for QA, In QA, QA Approved. Leave assignee on yourself for a test ticket and walk it from In Progress to QA Approved without ever changing the assignee. Then connect a GitHub repo and set a Git automation so that merging a PR moves the linked issue to Ready for QA. Open and merge a throwaway PR with Fixes ABC-123 in the description. What you are looking for: the issue transitions on merge with no manual click, and the assignee field never changes. That is the whole thesis made concrete: the handoff rode the build, and ownership stayed put.

See also

Sources

← All notes Read recent essays →