Show CI pipeline error when Secrets Manager access is denied by entitlement
What does this MR do and why?
Implements https://gitlab.com/gitlab-org/gitlab/-/work_items/613392.
Previously, when a CI job requested GitLab Secrets Manager (GSM) secrets but the namespace entitlement denied direct reads, gitlab_secrets_manager_payload in the build runner presenter returned {} and the runner failed the job with a generic secret-resolution error. The only signals of the denial were internal telemetry events. Users had no indication of why the job failed or how to fix it.
This MR fails the job at scheduling time with a dedicated, actionable failure reason instead:
- Adds a new failure reason
secrets_manager_access_denied(enum value1018inEnums::Ci::CommitStatus). It covers all entitlement denial reasons — trial expired, credits exhausted, on-demand disabled, subscription grace period expired, and beta program ended for non-opted-in namespaces — not just the beta cutoff. - Adds a check to
pre_assign_runner_checksinEE::Ci::RegisterJobService, following the same pattern assecrets_provider_not_found: if the build requestsgitlab_secrets_managersecrets andEntitlement#permits_direct_read?is false, the build is dropped with the new failure reason before runner assignment. - Fails open so job pickup never blocks on or fails because of CustomersDot: entitlement resolution uses
Entitlement.for!with a 0.25 s HTTP timeout and rescues all errors to permit scheduling. The check exits early for builds without GSM secrets, when thenative_secrets_managementlicense is absent, or when thesecrets_manager_paid_experiencefeature flag is disabled (the flag guard is required because the resolver returnsineligiblewhen the flag is off). Per-request memoization in the entitlement resolver caps CustomersDot calls at one per namespace. - Shows a user-facing message on the job page/log via
CommitStatusPresenter: "This job could not retrieve secrets because the namespace does not have access to GitLab Secrets Manager. To restore access, start a trial or purchase GitLab Secrets Manager for the top-level group." with a "How do I fix it?" link to a new troubleshooting docs section. A short status text is added inGitlab::Ci::Status::Build::Failed. - Keeps emitting the existing
DenialTelemetryinternal event, with a newjob_schedulingsurface so dashboards can distinguish it from the existingci_runner_payloadsurface. - The GraphQL
CiJobFailureReasonenum picks up the new value automatically;doc/api/graphql/reference/_index.mdis regenerated. - Adds a troubleshooting section to
doc/ci/secrets/secrets_manager/_index.md.
Notes on scope:
- The grace window (
blocked_reason: grace) still permits direct reads, so those builds are not dropped. This matches the presenter behavior. - The scheduling check does not replicate the presenter's
NamespaceEnrollmentcheck. Entitlement is the GA-era gate, and a denied-but-unenrolled namespace now gets an actionable message instead of a generic runner error.
References
- Work item: https://gitlab.com/gitlab-org/gitlab/-/work_items/613392
- Epic (contains the suggested error wording): https://gitlab.com/groups/gitlab-org/-/epics/22209
- Prior MR that added the entitlement gating: !249902 (merged)
- Review thread that raised this: !249902 (comment 3681378691)
Screenshots or screen recordings
Tested locally:
| Before | After |
|---|---|
![]() |
![]() |
How to set up and validate locally
-
Enable the feature flags in a rails console:
Feature.enable(:secrets_manager_paid_experience) Feature.enable(:secrets_manager) # if not already enabled -
On a project in a group with GSM provisioned, add a CI job that requests a GSM secret:
test_job: script: echo "test" secrets: DATABASE_PASSWORD: gitlab_secrets_manager: name: password -
Simulate a denied entitlement in a rails console. Entitlement is resolved from CustomersDot, so the easiest way locally is a temporary monkey-patch (or point your GDK at CustomersDot staging), for example:
SecretsManagement::Entitlement.define_singleton_method(:for!) do |*| SecretsManagement::Entitlement.new(state: :blocked, blocked_reason: :trial_expired) end -
Run a pipeline with a runner available. The job should fail immediately with failure reason
secrets_manager_access_denied, and the message should be visible on the job page. -
Run the specs:
bundle exec rspec ee/spec/services/ci/register_job_service_spec.rb -e 'GitLab Secrets Manager secrets defined'
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

