Build a stable key for each finding, such as repository plus package plus advisory id, store it on the ticket, and search for that key before you create anything. If a ticket exists, update it. Expect that search results can lag behind writes, so add a second guard.
Why duplicates happen
A scheduled scan finds the same vulnerable package every night. A webhook is retried and delivers twice. Two jobs overlap. Each of these runs the same create step again, and by Friday the backlog holds six copies of one issue and nobody trusts it. People then stop reading automation tickets at all, which defeats the point.
Step 1: define the key
The key should identify the unit of work, not the occurrence. For a dependency finding, a good key is the repository name, the package name and the advisory identifier. Leave out the scan date, the commit hash and the severity, since those change between runs while the work stays the same.
If two repositories carry the same vulnerable package, decide whether they are one ticket or two. Often one ticket per owning team is right. See one vulnerability, forty repos.
Step 2: store the key where you can search it
Put the key in a label, a dedicated custom field or the first line of the description. A field is better than free text because search on it is exact. In Jira, the issue search endpoint takes a JQL query, so a lookup such as one filtering on the project, the label and the status category works well.
Step 3: search, then decide
| Search result | Action |
|---|---|
| No ticket | Create one with the key |
| Open ticket | Add a comment only if something material changed, such as a new fixed version |
| Closed as fixed, finding is back | Reopen or create a linked regression ticket |
| Closed as accepted risk with a valid expiry | Do nothing |
| Closed as accepted risk, expiry passed | Reopen with a note |
The last two rows connect to exceptions. Our post on what should automatically revoke a vulnerability exception covers expiry rules.
Step 4: expect search lag
Atlassian's documentation for the enhanced JQL search endpoint warns that recent updates might not be immediately visible in the results, and offers a reconcileIssues parameter when you need read after write consistency. In a dedupe flow that matters: a retry that fires two seconds after the first create may search, find nothing and create a second ticket.
Defences that do not depend on the search index:
- Use a concurrency group so only one run per repository executes at a time. See stopping a job from running twice.
- Record created keys in a small store you control, such as a file in an artifact or a table, and check it first.
- Make webhook handling idempotent by remembering delivery ids, as in webhook replay and duplicate event handling.
Step 5: clean up the existing mess
Run the search once without creating anything, group results by key, and close duplicates with a link to the survivor. Do it in report only mode first so you can check the grouping.
Try it yourself
Click through it at your own pace, or open it full screen.
Look at how findings are grouped and imagine which field would serve as the key.
Safeguard groups and tracks dependency findings across repositories. Try the free scan at gold.safeguard.sh/scan.
Quick check
Why not use the scan date in the key?
Because the date changes every run, so every run would look like a new finding and create a new ticket.
Is searching enough to prevent duplicates?
No. Search results can lag behind writes, so combine it with concurrency control and idempotent handling.
Sources
Last reviewed: 19 October 2026.