MergeRequestsFinder ignores review_state when combined with a reviewer wildcard (ANY/NONE)

Summary

MergeRequestsFinder silently drops a positive review_state / review_states filter whenever a reviewer wildcard (reviewer_id: ANY / reviewer_id: NONE, or reviewerWildcardId in GraphQL) is also set. Only the wildcard applies, and the results are wrong.

Steps to reproduce

Run against the GraphQL API on gitlab.com (project gitlab-org/glql, open MRs, as of 2026-09-17):

query {
  project(fullPath: "gitlab-org/glql") {
    mergeRequests(state: opened, reviewerWildcardId: ANY, reviewState: APPROVED, first: 50) {
      count
      nodes { iid reviewers { nodes { username mergeRequestInteraction { reviewState } } } }
    }
  }
}

Returned 8 MRs. None of them has a reviewer in APPROVED state (they are UNREVIEWED, REVIEWED or REVIEW_STARTED). The result is identical to reviewerWildcardId: ANY alone.

mergeRequests(state: opened, reviewerWildcardId: NONE, reviewState: APPROVED, first: 50)

Returned 2 MRs that have no reviewers at all, which is impossible for an APPROVED review state.

The negated form is not affected: reviewerWildcardId: ANY, not: {reviewStates: UNREVIEWED} correctly excludes MRs with an unreviewed reviewer.

What is the current bug behavior?

review_state is ignored when combined with a reviewer wildcard. Only review_requested / no_review_requested is applied.

What is the expected correct behavior?

Either apply both filters (reviewer_id: ANY + review_state: X should return MRs with at least one reviewer in state X; reviewer_id: NONE + review_state: X should return nothing), or reject the combination with a validation error instead of returning wrong rows.

Root cause

In app/finders/merge_requests_finder.rb:

  • by_reviewer handles the wildcards with items.no_review_requested / items.review_requested and never looks at params.review_state. Only the named-reviewer branch passes it through (items.review_requested_to(params.reviewer, params.review_state)).
  • by_review_state then returns early because the wildcard sets reviewer_id:
def by_review_state(items)
  return items unless params.review_state.present?
  return items if params.reviewer_id? || params.reviewer_username?

  items.review_states(params.review_state, params.ignored_reviewer)
end

So when a wildcard is present, neither method applies the state filter.

Context

Found while adding a reviewState filter to GLQL (glql!508 (merged)). GLQL now rejects the reviewer = any|none + reviewState pairing at compile time as a workaround. That rejection can be relaxed to a redundancy warning for any once this is fixed.

Edited by 🤖 GitLab Bot 🤖