Pipeline page branch links return 404 due to GraphQL refPath returning git ref path instead of web URL
### Summary On the CI/CD pipelines page, clicking a branch name link results in a **404 error**. The link points to a URL like: ``` https://gitlab.com/<namespace>/<project>/-/refs/heads/<branch-name> ``` instead of the correct URL: ``` https://gitlab.com/<namespace>/<project>/-/commits/<branch-name> ``` or: ``` https://gitlab.com/<namespace>/<project>/-/tree/<branch-name>?ref_type=heads ``` This regression was introduced when the **Build > Pipelines** page migrated from REST to GraphQL data (behind the `pipelines_page_graphql` feature flag). The branch still exists and is accessible via the correct URL, but the link generated on the pipelines page is broken. *This issue was investigated with the help of GitLab Duo.* ### Steps to reproduce 1. Navigate to any project's **Build > Pipelines** page (e.g., `https://gitlab.com/gitlab-com/support/toolbox/engineering-directory/-/pipelines`) 2. Click on any **branch name** link next to a pipeline 3. Observe a **404** response **Example:** - Broken link: `https://gitlab.com/gitlab-com/support/toolbox/engineering-directory/-/refs/heads/metric-graphs` → 404 - Working link: `https://gitlab.com/gitlab-com/support/toolbox/engineering-directory/-/tree/metric-graphs?ref_type=heads` → works **To reproduce on self-managed 18.8.3**, both feature flags must be enabled: ```ruby Feature.enable(:pipelines_page_graphql) Feature.enable(:ci_pipeline_statuses_updated_subscription) ``` On 18.8.3, the GraphQL pipelines page entry point (`app/assets/javascripts/pages/projects/pipelines/index/index.js`) requires both flags: ```javascript const shouldUseGraphql = gon?.features?.pipelinesPageGraphql && gon?.features?.ciPipelineStatusesUpdatedSubscription; ``` ### Root cause The issue is a mismatch between what the GraphQL `refPath` field returns and what the frontend expects as a clickable web URL. #### Full trace **1. GraphQL fragment requests `refPath`** `app/assets/javascripts/ci/pipelines_page/graphql/fragments/pipeline_details.fragment.graphql`: ```graphql fragment PipelineDetails on Pipeline { ref # returns plain string: "metric-graphs" refPath # returns git ref path: "refs/heads/metric-graphs" } ``` **2. GraphQL `refPath` field resolves to `source_ref_path` on the model** `app/graphql/types/ci/pipeline_type.rb`: ```ruby field :ref_path, GraphQL::Types::String, null: true, description: 'Reference path to the branch from which the pipeline was triggered.', method: :source_ref_path # delegates to the model method ``` **3. `source_ref_path` returns a git ref, NOT a web URL** `app/models/ci/pipeline.rb`: ```ruby def source_ref_path if branch? || merge_request? Gitlab::Git::BRANCH_REF_PREFIX + source_ref.to_s # BRANCH_REF_PREFIX = "refs/heads/" # Result: "refs/heads/metric-graphs" -- git internal path, not a web URL elsif tag? Gitlab::Git::TAG_REF_PREFIX + source_ref.to_s end end ``` **4. The Vue component falls through to `refPath` as the link href** `app/assets/javascripts/ci/pipelines_page/components/pipeline_url.vue`: ```javascript commitRef() { return this.pipeline?.ref; // With GraphQL data, this is a plain string "metric-graphs" // NOT an object with .ref_url or .path properties }, refUrl() { return this.commitRef?.ref_url // undefined (string has no .ref_url) || this.commitRef?.path // undefined (string has no .path) || this.pipeline?.refPath; // falls through to: "refs/heads/metric-graphs" }, ``` The `|| this.pipeline?.refPath` fallback was added in !206072 to handle GraphQL data, but `refPath` resolves to `source_ref_path` which returns a git internal path, not a web URL. The browser resolves this relative URL against the project path, producing: `/-/refs/heads/metric-graphs` which returns **404** (no route handles this) #### Why it worked before (REST) The REST serializer in `app/serializers/ci/pipeline_entity.rb` exposed `ref` as a **nested object**: ```ruby expose :ref do expose :name do |pipeline| pipeline.ref # "metric-graphs" end expose :path do |pipeline| project_ref_path(pipeline.project, pipeline.ref) # "/-/commits/metric-graphs" -- correct web URL end end ``` With REST data, `pipeline.ref` was `{ name: "metric-graphs", path: "/-/commits/metric-graphs" }`, so `refUrl` resolved via `this.commitRef?.path` and never fell through to `refPath`. | | REST (before) | GraphQL (after) | |---|---|---| | `pipeline.ref` | `{ name: "metric-graphs", path: "/-/commits/metric-graphs" }` | `"metric-graphs"` (plain string) | | `pipeline.refPath` | N/A | `"refs/heads/metric-graphs"` | | `refUrl` resolves to | `this.commitRef?.path` then `"/-/commits/metric-graphs"` (correct) | `this.pipeline?.refPath` then `"refs/heads/metric-graphs"` (broken) | ### Introduced by - !206072 - "Move pipelines page to GraphQL - Part 1" (merged 2025-10-02, milestone 18.5). This MR migrated the **Build > Pipelines** page to use GraphQL data behind the `pipelines_page_graphql` feature flag. It modified `pipeline_url.vue` to add a `|| this.pipeline?.refPath` fallback in the `refUrl` computed property, which resolves to the git internal ref path (`refs/heads/...`) instead of a web URL when GraphQL data is used. - The underlying GraphQL field was added in !72591 (merged 2021-12-22) which mapped `ref_path` to `source_ref_path`, a method designed for internal git operations, not web URLs. **Note:** The issue description previously cited !213414 (`mr_pipelines_graphql` / MR Pipelines tab) as the introducing MR. That MR is unrelated to the **Build > Pipelines** page. The `mr_pipelines_graphql` feature flag controls the MR Pipelines tab and is **not enabled** on GitLab.com. The actual feature flag controlling the main pipelines page is `pipelines_page_graphql` (along with `ci_pipeline_statuses_updated_subscription` on 18.8.x). ### Affected versions - **Broken on**: GitLab.com (currently running with `pipelines_page_graphql` enabled), and any self-managed version >= 18.5 where both `pipelines_page_graphql` and `ci_pipeline_statuses_updated_subscription` feature flags are enabled (on 18.8.x, both are required; the entry point gate was later simplified) - **Reproducible on**: 18.8.3 self-managed with both feature flags enabled ### Proposal Update the GraphQL `ref_path` field in `app/graphql/types/ci/pipeline_type.rb` to return a proper web URL instead of the raw git ref path. For example: ```ruby field :ref_path, GraphQL::Types::String, null: true, description: 'Reference path to the branch from which the pipeline was triggered.' def ref_path return unless object.ref ::Gitlab::Routing.url_helpers.project_commits_path(object.project, object.ref) end ``` **Note:** Changing the return value of `ref_path` in GraphQL is a **breaking change** for any API consumers relying on the current `refs/heads/...` format. An alternative approach would be to add a new field (e.g., `ref_web_path`) and update the frontend fragment to use it, preserving backward compatibility.
issue