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:

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:

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:

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:

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:

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:

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 (merged) 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:

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 (merged) - "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) (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 (merged) (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:

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.

Edited Feb 10, 2026 by Kenneth Chu
Assignee Loading
Time tracking Loading