Include target project pipelines in merge request pipelines resolver

What does this MR do and why?

With mr_pipelines_graphql enabled, the MR Pipelines tab on a fork merge request drops every pipeline that ran in the target project. A pipeline created through "Run pipeline" never shows up in the table, so the button appears to do nothing. Closes #613453 (closed).

Background

On a fork MR, pipelines live in two projects. Regular MR pipelines run in the fork, and "Run pipeline" creates pipelines in the target project when the user can run pipelines there, so they can use the target project's runners and variables. The REST endpoint behind the legacy tab returns both sets. The GraphQL MergeRequest.pipelines field only ever returned the fork's. The scoping was first reported in #421742 (closed) back in 2023; #613453 (closed) has the full story.

Why the pipelines were dropped

The resolver ANDs two relations: Ci::PipelinesFinder, which hard-scopes to the source project, and merge_request.all_pipelines, which spans both projects. The union subquery fetches the target-project rows and the outer WHERE immediately discards them. The entire SQL diff from the first commit is one dropped condition:

-- before
WHERE project_id = <source_project_id> AND source != 12
-- after
WHERE source != 12

Screenshots

Local GDK setup: a public target project whose CI/CD is visible to project members only, and a private fork. The MR has two merged-results pipelines in the target project (created there the way "Run pipeline" does) and two pipelines in the fork. mr_pipelines_graphql is enabled in every screenshot.

Every screenshot shows the same MR. What changes per row is the signed-in user, and with them which of the two pipeline sets the page is allowed to show:

  • both projects: signed in as a reporter of both the target project and the fork, so both pipeline sets are readable
  • source (fork) only: signed in as a reporter of the fork only. This user can open the MR because the target project is public, but the target project's pipelines are members-only, so only the fork set is readable.
  • target project only: signed in as a reporter of the target project only, with no access to the private fork, so only the target set is readable
user can read pipelines in before this MR after this MR
both projects both-projects-flag-enabled both-projects-flag-enabled-after
source (fork) only fork-only-flag-enabled fork-only-flag-enabled-after
target project only target-only-flag-enabled target-only-flag-enabled-after

How to set up and validate locally

  1. Enable the flag on the target project of a fork MR: Feature.enable(:mr_pipelines_graphql, project).

  2. Make sure the MR's source branch has a .gitlab-ci.yml job that runs in merge request pipelines, for example:

    test-job:
      script: echo ok
      rules:
        - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  3. As a user who can run pipelines in the target project (the default "Run CI/CD pipelines in the parent project" setting must be on), open the MR's Pipelines tab and select "Run pipeline", confirming the fork security modal. This creates the pipeline in the target project.

  4. On master the new pipeline never appears in the table; on this branch it does. Disable the flag and reload to compare against the legacy tab.

Database

Raw SQL, before
SELECT "p_ci_pipelines".* FROM (
  (SELECT "p_ci_pipelines".* FROM "p_ci_pipelines" WHERE "p_ci_pipelines"."source" = 10 AND "p_ci_pipelines"."merge_request_id" = 515814427 AND "p_ci_pipelines"."project_id" IN (25685999, 278964))
  UNION
  (SELECT "p_ci_pipelines".* FROM "p_ci_pipelines" WHERE "p_ci_pipelines"."project_id" = 25685999 AND ("p_ci_pipelines"."source" IN (1, 2, 3, 4, 5, 6, 7, 8, 11) OR "p_ci_pipelines"."source" IS NULL) AND "p_ci_pipelines"."ref" = 'renovate-gems/redis-cluster-client' AND "p_ci_pipelines"."tag" = FALSE AND "p_ci_pipelines"."sha" IN ('a21305bad067a4ca2f66536d1894bf822621e2fb', '410596d6ab002174a6d38e203ae2acc2c0cb7291', '40c90da60c760535f65afe410ef030928157e266'))
) p_ci_pipelines
WHERE "p_ci_pipelines"."project_id" = 25685999 AND "p_ci_pipelines"."source" != 12
ORDER BY CASE "p_ci_pipelines".source WHEN (10) THEN 0 ELSE 1 END, "p_ci_pipelines".id desc, CASE "p_ci_pipelines".source WHEN (10) THEN 0 ELSE 1 END, "p_ci_pipelines".id DESC
Raw SQL, after
SELECT "p_ci_pipelines".* FROM (
  (SELECT "p_ci_pipelines".* FROM "p_ci_pipelines" WHERE "p_ci_pipelines"."source" = 10 AND "p_ci_pipelines"."merge_request_id" = 515814427 AND "p_ci_pipelines"."project_id" IN (25685999, 278964))
  UNION
  (SELECT "p_ci_pipelines".* FROM "p_ci_pipelines" WHERE "p_ci_pipelines"."project_id" = 25685999 AND ("p_ci_pipelines"."source" IN (1, 2, 3, 4, 5, 6, 7, 8, 11) OR "p_ci_pipelines"."source" IS NULL) AND "p_ci_pipelines"."ref" = 'renovate-gems/redis-cluster-client' AND "p_ci_pipelines"."tag" = FALSE AND "p_ci_pipelines"."sha" IN ('a21305bad067a4ca2f66536d1894bf822621e2fb', '410596d6ab002174a6d38e203ae2acc2c0cb7291', '40c90da60c760535f65afe410ef030928157e266'))
) p_ci_pipelines
WHERE "p_ci_pipelines"."source" != 12
ORDER BY CASE "p_ci_pipelines".source WHEN (10) THEN 0 ELSE 1 END, "p_ci_pipelines".id desc, CASE "p_ci_pipelines".source WHEN (10) THEN 0 ELSE 1 END, "p_ci_pipelines".id DESC

Plans on the production CI clone: before | after

References

  • Closes #613453 (closed)
  • Original 2023 report of the resolver scoping: #421742 (closed) (closed in June 2025 as no longer valid, but the resolver was unchanged)
  • mr_pipelines_graphql rollout: #419726
Edited by Sahil Sharma

Merge request reports

Loading
Loading