Fix duplicate Jira comments from re-processed cross-references

What does this MR do and why?

Fixes the long-standing bug where the Jira integration posts duplicate Jira comments for the same commit and "reopens" resolved Jira issues (resets a remote link's resolved status and spams the issue history) whenever the same commit SHA is re-processed: fast-forward merges, source-branch deletion, rebases, or the same SHA pushed to a second project.

Root cause

GitLab keeps no record of cross-references sent to external trackers (ExternalIssue#notes returns Note.none, so the cross_reference_exists? dedup in Mentionable#create_cross_references! never filters anything). Every re-push re-runs Integrations::CreateExternalCrossReferenceWorker, and deduplication falls entirely on two fragile Jira-side checks in Integrations::Jira:

  1. comment_exists? matched the exact comment body, which embeds volatile context: the branch name resolved from the SHA at processing time (first_ref_by_oid) and the author link. After a fast-forward merge deletes the source branch, the body no longer matches and a duplicate comment is posted.
  2. send_message unconditionally re-saved an existing remote link with resolved: false, which visibly reopens resolved Jira issues and adds a history entry on every re-push.
  3. Both lookups failed open: jira_request rescues all errors and returns nil, which was indistinguishable from "nothing exists", so a transient API error produced a duplicate comment and a duplicate remote link.

The fix

  • Dedup mention comments on the stable entity URL (comment.body.include?("|#{entity_url}]"), matching the Jira wiki-markup link every mention comment contains) instead of the exact body. This also dedups against comments posted by older GitLab versions.
  • The mention path no longer touches an existing remote link at all (update_existing_remote_link: kwarg, default false). Only the close-issue path passes true, so marking a link resolved: true still works.
  • Lookups now fail closed: a remote-link lookup error aborts the whole send (raises into the surrounding jira_request), and a failed comment lookup is treated as "comment exists". A transient error can now skip a notification, but never duplicate one.

Verification against a real Jira Cloud instance

Beyond the specs (written first; they reproduced both defects on the unfixed code), the fix was verified empirically against a Jira Cloud dev site:

  • End-to-end repro: a commit mentioning a Jira issue pushed on a branch produced 1 comment + 1 remote link; after a fast-forward merge to the default branch plus source branch deletion, ProcessCommitWorker (default=true) and CreateExternalCrossReferenceWorker both re-ran, and no duplicate comment was posted and the remote link was untouched. Pre-fix, this exact sequence produced the duplicate.
  • Remote-link semantics: Jira Cloud ignores the GlobalID key GitLab sends (the API field is the case-sensitive globalId), so remote links are never upserted server-side; they accumulate. Jira also does not dedup remote links by URL, so every fail-open lookup miss permanently added a duplicate link. Both findings shaped the fix (URL-keyed dedup + fail-closed lookups).

Trade-offs (deliberate)

  • A remote link's title is no longer refreshed by re-mentions (for example, after an MR title edit). Accepted: it was an accidental side effect of the buggy re-save, and refreshing it is what reopened resolved issues.
  • A prior "Issue solved with [sha|url]." comment suppresses a later mention comment for the same commit, which is arguably correct, since Jira was already told about that commit.
  • No feature flag: the behavior removed (re-saving existing remote links with resolved: false, duplicate comments) is the defect itself, and the empirical verification above refuted the one scenario where skipping updates could have masked legitimate behavior. Happy to add one if reviewers prefer for the stable backports.

Known residual limitations (follow-up material)

  • Dedup is still check-then-act against remote Jira state: concurrent or retried Sidekiq jobs can race between the lookup and the post. The proper fix is persistent GitLab-side tracking of sent external cross-references (parity with internal-issue dedup).
  • The comment container in Jira's issue response can be truncated on very busy issues; the URL-keyed remote-link guard (returned unpaginated by Jira) backstops that case.

References

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist.

Edited by Jorge Tomás

Merge request reports

Loading
Loading