← The ADLC library
Failure modes · 7

The orphan pull request problem

A third of the changes going into production have no ticket attached. That is not a hygiene issue. It is a hole in every number your organisation reports about itself.

A platform team runs a small audit before a compliance review. The task is boring: for every change deployed to production in the last quarter, show the ticket it belongs to, the review that approved it, and the requirement it satisfies.

The review and the deployment are easy. Both are recorded automatically.

The ticket is where it falls apart. Out of a few hundred merged pull requests, roughly a third have no ticket reference anywhere: not in the branch name, not in the title, not in the description, not in a commit message. Some of them are trivial. Several of them are not. One of them changed how session expiry is calculated. One of them altered a retry policy against a payment provider. One of them, and this is the one that ends the meeting early, disabled a validation check that had been added deliberately eighteen months earlier, with a comment explaining why, which the change removed along with the check.

Every one of these was reviewed. Every one was approved by a competent person. None of them was recorded anywhere except in the repository.

The question in the room is not “who did this.” It is much worse: “what else is in that third.”

Where orphans come from, honestly

It is tempting to treat unticketed work as indiscipline. It is mostly not. There are about five origins and only one of them is carelessness.

The change was too small to justify a ticket. Someone noticed a typo in a log message, a wrong constant, a missing null check. Creating a ticket, assigning it, moving it through three states, and closing it is a minute of overhead for a thirty-second fix. Every reasonable engineer skips it. Multiply by a fast-moving team and this alone accounts for a large share.

The work spun out of other work. A pull request opened to fix something surfaced a second problem. The second fix went in its own branch, correctly, and there was never a moment where it felt like a new piece of planned work. It felt like part of what was already happening.

The ticket existed but the reference did not land. Someone knew the ticket number, meant to put it in, and the branch was already created. Or the tooling references it in a way the audit did not parse. This is the least interesting cause and the most common one people assume, and it is usually a smaller slice than expected.

Operational and urgent work. An incident happens at eleven at night. Somebody fixes it. The ticket is created after the fact, or it is not, and the retro captures the incident but never links it to the specific change. Urgent work is systematically the least well recorded and systematically the most consequential.

And agent-generated follow-ups. This is the newer one. When work is cheap, follow-ups proliferate: adjustments, stabilisations, small corrections, cleanups. Each is individually too small for a ticket by the reasoning in the first case. Collectively they can represent a substantial share of the diff that actually shipped, and none of it is on the board.

Notice that four of these five are the result of people behaving sensibly. That matters for the fix, because a problem caused by sensible behaviour will not be solved by asking people to behave better.

Too small to justify a ticketA typo, a wrong constant, a missing null check. Three state transitions to record a two-minute fix.
Spun out of other workA fix surfaced a second problem. There was never a moment where filing a ticket was the natural act.
The ticket existed, the reference did not landBranch already created, or the reference sits somewhere the audit query does not look.
Operational and urgentEleven at night, somebody fixes it. The retro captures the incident and never the change.
Agent-generated follow-upsThe newer one. Cheap work proliferates: adjustments, stabilisations, cleanups, each too small to file.
Four of the five are people behaving sensibly. A problem caused by sensible behaviour will not be fixed by asking people to behave better.

What the hole actually costs

The compliance angle is the most vivid, so people reach for it first, and it is real. If you cannot connect a production change to an authorising requirement, you cannot answer a control question about change management, and every regulated framework has one. But I think the compliance cost is not the main cost, because it is episodic. It hurts twice a year during an audit.

The costs that hurt continuously are quieter.

Every delivery metric is computed over a partial denominator. Cycle time, throughput, lead time, whatever your organisation reports, is measured over ticketed work. If a third of the change volume is unticketed, the metrics are not slightly noisy, they are describing a different population than the one that exists. Worse, the unticketed population is not a random sample. It is biased toward small, urgent, and reactive work, which means your metrics systematically understate exactly the category of work that is most disruptive to plan.

Capacity models are wrong in a specific direction. If the board only sees two-thirds of what the team does, then the team’s apparent capacity is lower than its actual output, and the difference is invisible work that will keep happening. So every plan is built on the assumption that the invisible third does not exist, and every plan is disappointed, and the disappointment is attributed to estimation error rather than to an unmeasured workload. Teams get labelled as slow when they are in fact spending a third of their time on real work nobody counted.

Debugging loses its trail. Six months later, someone asks why session expiry works the way it does. The answer, if it exists, is in a pull request title and maybe a review comment. There is no ticket carrying the reasoning, no linked discussion, no acceptance criteria. Institutional memory is thinner than anyone realises, and the orphan changes are precisely the ones with no memory attached.

And incident analysis gets harder. When something breaks, the first question is what changed. If the change log is complete you can answer it. If a third of changes are only in the repository and the repository is not what anyone searches, you are reconstructing history from commit archaeology under time pressure, which is a genuinely miserable way to spend an outage.

Metrics over a partial denominator

Cycle time and throughput computed over ticketed work only. A third of changes are simply not in the sample.

Capacity understated

The board sees two-thirds of the output, so apparent capacity is lower than actual, in a consistent direction.

No debugging trail

Why does session expiry work this way. The answer is a PR title and maybe a review comment.

Incident analysis blind

The first question is what changed. A third of the answer is only in the repository.

The compliance cost is the vivid one. These four are the ones that charge you every week.

The two wrong fixes

The first wrong fix is enforcement: block merges without a ticket reference. Some teams do this and it does produce a beautiful compliance number.

It also produces a rich ecosystem of junk tickets. When the cost of merging without a ticket is a blocked merge, people create tickets, and the tickets they create under duress are titled “fix” and “small changes” and “misc” and they carry no information whatsoever. You have not recovered the missing context. You have converted an honest gap into a dishonest record, which is worse, because now nobody knows the record is incomplete. A board full of empty tickets looks like coverage.

The second wrong fix is a hygiene campaign: everyone goes back through the last quarter and links things up. This works for about three weeks and provides a useful one-off view. It does not survive contact with a busy month, for exactly the reason that all manual reconciliation fails: the person doing the linking gets no benefit from having done it.

Inference is the interesting option

The version that actually works is to stop asking humans for the link and start deriving it.

There is quite a lot of signal available for connecting an unreferenced pull request to the work it belongs to. The files it touches, compared against files touched by other work on the same feature. Timing, against when tickets were active. The author, against what they were assigned. Branch lineage. Text similarity between the pull request description and ticket content. Whether the change reverts, extends, or repairs a change that was itself linked.

None of these is conclusive alone. Together they are frequently enough to propose a link with reasonable confidence, and, importantly, to say when they are not enough. This is one of the things GroundTruth does: it reads the repository and proposes ticket associations for pull requests that never referenced one, along with a confidence and the evidence behind it.

But I want to be careful about how this is framed, because the framing matters more than the capability.

The valuable output is not the automatic link. It is the classification. Of the third that had no ticket, you want to know: which of these are confidently attributable to known work, which are confidently trivial, and which are neither. That last bucket is the one that matters. It is usually small, often a few percent of total volume, and it contains the session-expiry change and the disabled validation check. Those are the ones a human should look at, and the reason the audit meeting was so uncomfortable was not that a third of changes were unticketed, it was that nobody had ever separated that third into these three piles.

A tool that silently links everything and reports 100 percent coverage has destroyed the signal you needed. The uncertainty was the useful part.

The valuable output is not the automatic link. It is the classification: which changes are confidently attributable to known work, which are genuinely unattached, and which are ambiguous enough that a person should look.

A tool that links everything and reports full coverage has destroyed the signal you needed. The uncertainty was the useful part.

The trivial-work question

Underneath all of this is a question teams rarely answer explicitly: should trivial work have tickets at all?

I think the honest answer is no, and that the desire to record everything is a compliance instinct rather than an engineering one. A typo fix in a log message does not need a ticket, and demanding one is how you get the junk-ticket ecosystem.

But “trivial” needs a definition that is not “small,” because size is not the risk axis. The session-expiry change was probably a handful of lines. What made it non-trivial was the category it touched: authentication behaviour.

So the rule I would actually defend is that work needs a recorded authorisation when it changes something in a sensitive category, regardless of size, and does not otherwise. Auth, permissions, data handling, external interfaces, money, defaults, anything with a regulatory story attached. Everything else can merge on the strength of the pull request alone, and the repository is a perfectly good record of it.

That is a much smaller ask than “ticket everything,” it targets the actual risk, and it is mechanically checkable, which means it can be enforced without relying on anyone’s memory.

Where this breaks down

Three limits, and the first one is a direct challenge to the premise.

Maybe the ticket is the wrong system of record. I have written this as though the gap is a problem to be closed by connecting repository to board. There is a serious counter-argument: the pull request already contains the change, the discussion, the review, and often the reasoning, and it is created automatically as a byproduct of doing the work. The ticket is a hand-maintained parallel record that exists mostly for people who do not read repositories. A team could reasonably decide that the pull request is the record, invest in making pull request descriptions carry the context that tickets currently carry badly, and treat the board as a planning tool rather than a historical one. That is a coherent position, it eliminates the orphan problem by definition, and it fails only where an external party requires a record in a specific form. Which, to be fair, is often.

Inferred links can be confidently wrong, and confident wrongness is expensive here. A proposed association between a pull request and a ticket is a guess. When it is right, it is useful. When it is wrong, it puts work under the wrong feature, corrupts the attribution, and, if anything downstream acts on it, moves the wrong ticket. The failure is quiet, because a wrong link looks exactly like a right one in the interface. Any system doing this should show its evidence and its confidence, should be run in a shadow mode long enough to measure its accuracy against reality before it writes anything, and should decline to guess rather than guess weakly. A system that always produces an answer is a system you cannot trust.

And full traceability has a real cost that is rarely counted. There is a version of this that ends with every change carrying an authorisation trail, and it is slower, heavier, and more bureaucratic than what most teams have now. For a medical device or a payments core, that is correct and the cost is worth paying. For a team building internal analytics tooling, imposing it produces overhead with no corresponding risk reduction, and the most likely outcome is that people route around it. Traceability should be proportional to consequence, and the failure to make it proportional is why so many traceability initiatives are quietly abandoned in year two.

The takeaway

Orphan pull requests are not a discipline problem. They are the predictable output of sensible people avoiding overhead that has no local benefit, plus a new volume of small agent-generated follow-ups that nobody would sensibly ticket. Enforcement converts the gap into junk records, and hygiene campaigns do not survive a busy month.

The useful move is to derive the links where they can be derived, be explicit about confidence, and focus human attention on the residue: the changes that are neither confidently attributable nor confidently trivial. And separately, to require recorded authorisation based on the category a change touches rather than on its size, because the risky change is almost never the big one.

The next piece stays with things that were never written down, and looks at a failure that costs more than any of these: the cross-team dependency nobody declared, discovered at the point where it is most expensive to discover.