Fix policy approval rule desync detection and resync failures
What does this MR do and why?
The security_policy_target_branch_desync_detection feature flag was rolled out to 10% and 25% on GitLab.com on 2026-09-01 and rolled back the same day. It caused two problems: the resync worker raised ActiveRecord::RecordInvalid on "sourceless" MR rules (rules whose approval_merge_request_rule_source row was lost to a cascade delete or a failed sync), leaving affected MRs permanently stuck in checking; and the detection ran on merged and closed MRs too, because mergeabilityChecks executes all checks regardless of state, so it logged security_policy_approval_rules_desync_detected and enqueued no-op resyncs for nearly every merged or closed MR in a policy-covered project.
This MR fixes both. The detection now only runs against open MRs and ignores legacy project rules with no approval_policy_rule_id (policy sync never copies those to MRs, so they were flagged forever). The rule sync now looks for a sourceless MR rule matching the uniqueness key before creating a new one, so it heals and relinks the existing rule instead of hitting the name uniqueness validation. The worker also rescues ActiveRecord::RecordInvalid and reports it via Gitlab::ErrorTracking instead of retrying, since the check re-enqueues the worker anyway.
check_security_policy_violations_service.rb:approval_rules_out_of_sync?returnsfalseunless the MR is open.ee/merge_request.rb:missing_policy_approval_rules?excludes project rules with a nilapproval_policy_rule_id.approval_project_rule.rb:merge_request_report_approver_ruleadopts a matching sourceless MR rule before building a new one, soupdate!relinks the source instead of duplicating the rule. Shared by every sync entry point (MR create, retarget, reopen, policy sync), not just the worker.resync_merge_request_rules_worker.rb: rescuesActiveRecord::RecordInvalid, tracks it viaGitlab::ErrorTracking.track_exceptionwithmerge_request_id, and still callsschedule_policy_synchronization.
References
- Rollout issue: #623321 (closed)
- Original bug: #601181 (closed)
- MR that introduced the detection: !246788 (merged)
- Related issue on duplicate/sourceless rules: #617882
- Related open MR that prevents new sourceless rules: !253949 (merged)
Screenshots or screen recordings
Not applicable. This is a backend fix with no UI changes.
How to set up and validate locally
This requires Ultimate, a project with a protected main branch, and a merge request approval policy requiring approvals on protected branches. Enable the flag for the project:
Feature.enable(:security_policy_target_branch_desync_detection, Project.find(<id>))-
Sourceless rule repro. Open an MR targeting
mainso it picks up policy rules, then delete its source rows:mr.approval_rules.report_approver.each { |r| r.approval_merge_request_rule_source&.delete }Before this MR:
MergeRequests::Mergeability::CheckSecurityPolicyViolationsService.new(merge_request: mr, params: {}).execute.status # => :checking Security::ScanResultPolicies::ResyncMergeRequestRulesWorker.new.perform(mr.id) # raises ActiveRecord::RecordInvalidAfter this MR, the worker succeeds,
mr.approval_rules.report_approver.map(&:approval_project_rule)are all present with the rule count unchanged, and the check no longer returns:checkingfor the desync reason. -
Merged MR repro. Take a merged MR in the project and run the check service on it. Before this MR it returns
:checkingand logs asecurity_policy_approval_rules_desync_detectedline. After this MR it returns:inactivewith no log.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
- No database changes or migrations.
- No documentation changes.
- No UI changes.
- Commit carries
Changelog: fixedwithEE: true, so no separate changelog entry is needed. - Tests added: check service spec (merged/closed MRs return inactive, no log, no enqueue), merge request spec (legacy nil-policy-rule rule alone does not trigger detection; a synced rule that lost its source still does), approval project rule spec (sourceless rule is adopted and relinked, no duplicate), worker spec (
RecordInvalidis tracked and does not raise, sync still scheduled). 76 examples pass, RuboCop clean.