Finding the work Jira never recorded
Orphan work is invisible to JQL by definition, because there is no issue to query. Here is how to find it from the code side, classify it properly, and fix the causes rather than the symptom.
Something shipped last quarter that nobody can find a ticket for. You know it happened, the code is in the repository, and when you go looking in Jira there is nothing. Multiply that by every incident fix, every “quick change” and every piece of infrastructure work, and the board stops being a description of what your team did.
You cannot find this from inside Jira
This is the part that costs teams the most time, so it is worth stating flatly. JQL queries issues. An orphan is work with no issue. There is no query that returns things that do not exist, and no amount of cleverness with filters will produce one.
Every attempt to solve this from the Jira side ends up measuring something adjacent: issues without a linked branch, issues closed without a status transition, issues missing a field. Those are real problems and none of them is the one you have. Orphan work is defined by its absence from the tracker, so the search has to start from a system that did see it happen.
That system is source control. Your repositories hold a complete, timestamped, non-negotiable record of every change that shipped. It is the only inventory in your organisation that nobody can forget to update, because the change does not exist until it is written there.
So the method is: enumerate from the code, resolve against Jira, and look at what fails to resolve.
The four populations, which need different fixes
Lumping all untracked work into one bucket produces a meeting where everyone agrees it is bad and nothing changes. Separate them.
Merged with no key
A pull request went in and carries no Jira issue key anywhere. Usually a convention failure, occasionally deliberate.
Bypassed the pull request
Commits pushed straight to a protected branch. Almost always incident response, and almost always the change you most need recorded.
Key that does not resolve
A key is present but points at nothing: wrong project prefix, typo, or an archived project. Looks linked on a dashboard, is not.
Issue with no code
The reverse orphan. An issue marked done with nothing in its development panel. Either the work was not code, or the link broke.
The second one deserves particular attention. Direct pushes to a protected branch are usually a person under pressure at 2am doing exactly the right thing for the customer. Treat that population as a process finding, never as a discipline finding, or you will teach people to route incident fixes through paths that hide them better.
Running the sweep
Enumerate merged changes. Take a window, say ninety days, and pull every merge into your default and release branches from GitHub, GitLab or Bitbucket. All three expose this over an API and all three have a supported command line client. Capture the pull request identifier, title, description, branch name, author, merge timestamp and target branch.
Enumerate direct pushes separately. Commits on a protected branch with no associated pull request are a different query and often a different code path in your script. Do not skip this because it is fiddly. It is where the interesting findings live.
Extract issue keys. Jira keys have a predictable shape, an uppercase project prefix, a hyphen and a number, which makes them straightforward to find in a branch name, a commit message, a pull request title or a pull request description. Search all four. A team that puts the key only in the branch name and a script that only reads descriptions will report a catastrophe that is not happening.
Resolve every key against Jira. This is the step people skip and it changes the answer materially. Jira’s REST API will tell you whether a key resolves, which project it belongs to and what type of issue it is. Confirm the key exists, and confirm it belongs to a project in scope. Otherwise you count references to a decommissioned project, keys from a different Jira site, and typos as successful traceability.
Write one row per change. Identifier, repository, author, date, extracted keys, resolved keys, classification. The ratio is a summary over that table. The table is the thing you will use.
Fixing the causes, cheapest first
Make the key easy to produce. Most missing keys are not defiance, they are friction. If getting the key into the branch requires a person to switch windows, find the ticket and retype it, some proportion will not happen. A saved branch-name format, an editor snippet, a repository template that puts the key prompt in the pull request body, all cost nothing and move the number.
Warn before you block. A pipeline step that leaves a comment when no key resolves, without failing the build, gets most of the benefit and none of the resentment. Blocking merges on a linking rule in the first month is how you get NOTICKET-1 created as a real Jira issue by an irritated engineer, at which point your data is worse than when you started.
Automate the retrospective link, not the retrospective ticket. Jira automation can react to events from a connected source control integration and comment, transition or link. That is useful. Resist the temptation to have it auto-create an issue for every unlinked merge: you get thousands of empty tickets nobody grooms, and the board becomes less trustworthy rather than more.
Give incident work a fast path. If your out-of-hours process requires creating a ticket before the fix, it will be violated, and correctly so. Instead make a post-incident issue trivial to create with the merge already attached, and accept that the record arrives an hour late.
Then consider tooling. If you want the join maintained continuously rather than swept quarterly, that is what this category of product does, ours included: GroundTruth reads GitHub, GitLab and Bitbucket and keeps the mapping against the tracker current, with full two-way sync for Jira, Linear and Azure DevOps. The sweep above is genuinely sufficient for a one-off audit, and if that is what you need, run it and skip the purchase.
Where this breaks down
Not all work is code, and the code side cannot see the rest. Configuration changes made in a vendor console, database fixes applied by hand, infrastructure altered through a cloud provider’s web interface: none of it appears in a repository, so none of it appears in this sweep. If your organisation does significant work that never touches a repository, this method finds a subset of your orphans and quietly reassures you about the rest.
Squash workflows can strip the evidence. If merges squash into a generated commit message, the key may survive only in the pull request record. That is fine while your source control host holds it, and it means a repository clone alone will not reconstruct the mapping. Check this before promising anyone an audit trail that survives a migration.
Monorepos make attribution ambiguous. One merge can touch four teams’ code. Which team’s orphan is it? There is no clean answer, and any per-team breakdown over a monorepo is an estimate dressed as a measurement. Say so when you present it.
A hundred percent is the wrong target and pursuing it is harmful. Some work genuinely should not have a ticket. A typo fix in a comment does not need a planning artefact, and demanding one is how you get teams optimising for the audit rather than the customer. Decide what your exclusions are, write them down, and measure against that rather than against perfection.