How to measure your Asana linking rate
A repeatable way to find out what share of your merged code traces back to an Asana task, why the number is harder to get in Asana than in a ticket-key tracker, and what to do with the answer.
You want one number: of the code that shipped last month, what proportion can be traced back to a task somebody planned? In a tracker with short issue keys this is tedious. In Asana it is genuinely awkward, and most teams give up before they get a figure. Here is how to get one anyway.
The one fact about Asana that shapes everything else
Asana identifies tasks by a long numeric id, visible in the task URL. There is no short, memorable, typeable key of the sort PROJ-412 that a developer can put in a branch name from memory.
That is not a defect. Asana was built for work that is not code, and a project-scoped key would be a strange thing to impose on a marketing calendar. But it means the convention that makes linking cheap everywhere else is unavailable to you by default. Nobody is going to type a sixteen-digit number into a branch name, and if they do they will get a digit wrong.
So the first decision is not how to measure. It is what your join key is going to be, because you cannot measure a link that does not exist.
Pick a join key you can actually enforce
Three options, in ascending order of reliability.
Paste the task URL into the pull request description. Lowest friction, works today, no configuration. Asana’s source control integrations are built around roughly this idea, and if you already run one, the habit may partly exist. The weakness is that it is a manual paste into a free-text field, so it fails silently and it fails most on the changes made in a hurry, which are the ones you most want traced.
Put the pull request URL on the task. A custom field or a comment on the Asana side, populated by whoever picks the work up. This inverts the failure mode: you get good coverage of planned work and no visibility at all of code that had no task, which is exactly the population you are trying to find. Useful as a secondary check, useless as the primary.
Derive a short key of your own and require it in the branch name. A tiny lookup table, or a short prefix plus the last six digits of the task id, generated by a script or a saved Asana rule so nobody types it by hand. More setup, and it is the only one of the three that a machine can check at push time.
Join keys that decay
- Task URL pasted by hand into a free-text field
- Anything a developer must retype from memory
- A link recorded only on the Asana side
- A convention with no check anywhere in the pipeline
Join keys that hold
- Generated, copied, never typed
- Present in the branch name, so it rides along on every commit
- Recorded on the code side, where the population you are measuring lives
- Checked by something automatic, even if the check only warns
Build the measurement on the code side
The measurement cannot live in Asana. Asana’s reporting can chart anything Asana knows about, and it does not know which pull requests merged into your repositories this month. Your denominator lives in source control, so the job runs there.
Define the denominator first, and write it down. Merged pull requests into your default and release branches, over a fixed window, excluding: bot and dependency-update pull requests, automated release and back-merge commits, and reverts. Excluding reverts is arguable, so pick one and be consistent, because a linking rate that moves because you changed the denominator is worse than no linking rate.
Pull the population. GitHub, GitLab and Bitbucket all expose a list of merged pull requests or merge requests with their descriptions over an API, and all three have a supported command line client. Fetch number, title, description, author, merge date and target branch. Do not scrape the web interface.
Extract candidate task references. Here is where people go wrong: they write an Asana URL pattern from memory. Do not. Open three real tasks in your own workspace, copy the URLs, and build the pattern from what you actually see, then keep a test case for each shape. Asana has changed its URL format before, so a pattern that worked eighteen months ago may miss everything created since, and a regex that silently matches nothing looks exactly like a team that has stopped linking.
Resolve, do not just match. A string that looks like a task reference is not a link. Call the Asana API for each extracted id and confirm the task exists and sits in a project you care about. Pasted-and-edited URLs, references to a colleague’s personal list, and tasks in an unrelated workspace all match the pattern and none of them are traceability.
Store the result, not just the ratio. Write one row per merged pull request: identifier, repository, author, merge date, resolved task id or null. The ratio is a group by over that table. The rows are what you will actually use.
Reading the number without misusing it
The absolute value tells you less than people expect. A first measurement in the fifties is normal and is not a verdict on anyone. What matters is the shape.
Trend, not level. One number is a baseline. Three months of numbers is information.
Split by repository before you split by person. Linking rate varies enormously by repository, because tooling, review culture and change type vary by repository. A cross-team comparison that ignores this mostly measures which team owns the infrastructure monorepo.
Never put it on an individual’s review. The moment linking rate is a performance metric, you get a hundred percent linking rate and a pile of tasks created after the fact to satisfy it. You will have destroyed the only signal you had, and you will not find out for two quarters.
The useful artefact is the unlinked list, not the percentage. Twenty merged pull requests with no task, sorted by size, read out in a retrospective, produces better conversations than any dashboard. Half of them will have an innocent explanation. The other half are the reason you started.
What to do about it, in cost order
Start with the convention, because it is free. Agree that the task URL goes in the pull request description, add it to your pull request template so the prompt is in front of everyone, and let the first month be terrible.
Then add a warning. A pipeline step that comments on a pull request with no resolvable task reference, and does not block the merge, moves the rate a long way on its own. Blocking merges on a linking rule early is how you get people writing no task in the description forever.
Then, if you want this maintained rather than owned, tools in this category keep the join current for you. Ours does: GroundTruth reads GitHub, GitLab and Bitbucket and joins the result to the tracker. Be precise about depth when you evaluate anything here, including us. Full two-way sync covers Jira, Linear and Azure DevOps. Asana is read plus coarse write, meaning it can read tasks and make simple updates but does not drive Asana’s richer structures. For measuring a linking rate, read access is all you need. For anything that writes back into Asana, ask exactly what “write” covers before you plan around it.
Where this breaks down
A linked pull request is not a justified one. Everything above measures whether a reference exists, not whether the task described the work, or whether the work was worth doing. A team can reach ninety percent linking with tasks that say “fix stuff”. The number is a floor on traceability, not a ceiling on quality.
Asana was not built to be a code tracker, and forcing it can be the wrong call. If your engineering work genuinely wants issue keys, branch conventions and pull request lifecycle automation, the honest recommendation is sometimes to run engineering work in a tracker designed for it and keep Asana for the cross-functional programme. Measure first, then decide.
The resolution step needs credentials somebody has to own. An API token with read access to your workspace now exists in a scheduled job. That is a small but real security surface, it will expire at an inconvenient moment, and the job will fail quietly if nobody is watching it. Least privilege, and alert on job failure rather than on the metric.
Squash and rebase workflows can hide the mapping. If your pull requests squash into a single commit with a generated message, the reference may live only in the pull request record and not in the repository history at all. That is fine while the pull request data exists, and it means a repository clone alone cannot reconstruct the link. Worth knowing before you promise anyone an offline audit trail.