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
- Navigate to any project's Build > Pipelines page (e.g.,
https://gitlab.com/gitlab-com/support/toolbox/engineering-directory/-/pipelines) - Click on any branch name link next to a pipeline
- 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_graphqlfeature flag. It modifiedpipeline_url.vueto add a|| this.pipeline?.refPathfallback in therefUrlcomputed 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_pathtosource_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_graphqlenabled), and any self-managed version >= 18.5 where bothpipelines_page_graphqlandci_pipeline_statuses_updated_subscriptionfeature 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.