Remove cancel_pipelines_when_policy_disabled feature flag
What does this MR do and why?
Removes the cancel_pipelines_when_policy_disabled feature flag (introduced in 19.1 as gitlab_com_derisk, promoted to beta and enabled by default in 19.2), making the behavior unconditional: running policy schedule pipelines are always cancelled when a pipeline execution schedule policy is disabled or unlinked from a project.
Changes:
- Delete the flag definition YAML.
- Remove the
Feature.enabled?guards inSecurity::SecurityOrchestrationPolicies::BaseProjectPolicyServiceandSecurity::PipelineExecutionPolicies::CancelPolicyPipelinesService. - Remove the flag-disabled spec contexts, plus the project-scoped-configuration context that only existed to cover the flag's namespace fallback.
The flag was default_enabled: true from 19.2, so this removal is a no-op at runtime and is zero-downtime-upgrade safe. Changelog: other follows the flag-removal flowchart for a default-on flag removed keeping the new code.
References
- Feature issue: #560629 (closed)
- Flag introduced by: !236748 (merged)
- Flag enabled by default: !244641 (merged)
Screenshots or screen recordings
Screen recording: N/A — this MR changes no frontend code and adds no interaction; the only observable effect is a backend state transition on a pipeline, captured below.
Verified on the GDK with the flag deleted from the codebase — no flag set anywhere. A schedule policy scoped to target-project produced pending pipeline #842; disabling the policy cancelled it within seconds, via the real chain (CancelPolicyPipelinesWorker ran with root_caller_id: Security::PersistSecurityPoliciesWorker).
| Before | After |
|---|---|
Policy enabled ![]() |
Policy disabled ![]() |
Pipeline #842 pending ![]() |
Pipeline #842 cancelled ![]() |
How to set up and validate locally
Adapted from !236748 (merged), minus the feature flag step: the behavior is now unconditional, so no Feature.enable call is needed.
Step 1: Create a group with a security policy project
-
Create a group.
-
Create a project named
security-policiesin that group. -
Create another project named
target-projectin that group. Note its ID. -
Add a
policy-ci.ymlfile tosecurity-policieswith a job that stays pending, so it is still cancelable when you disable the policy. A tag no runner has is more reliable than a sleeping job, which a local runner can claim and fail:policy-long-running-job: tags: [no-runner-has-this-tag] script: - echo "waiting" -
Add a
.gitlab/security-policies/policy.ymlfile tosecurity-policies(replaceGROUP_PATHwith your group's path andTARGET_PROJECT_IDwith the ID from above):--- experiments: pipeline_execution_schedule_policy: enabled: true pipeline_execution_schedule_policy: - name: Test Schedule Policy description: Policy for testing cancellation enabled: true content: include: - project: GROUP_PATH/security-policies file: policy-ci.yml schedules: - type: daily start_time: '10:00' time_window: value: 600 distribution: random policy_scope: projects: including: - id: TARGET_PROJECT_ID -
On the group page, go to Secure > Policies and link
security-policiesas the security policy project.
Step 2: Prepare a merge request that disables the policy
- On the group's Secure > Policies page, select Test Schedule Policy, then Edit policy.
- Select Disabled, then Update via merge request.
- Do not merge the merge request yet.
Step 3: Verify the cancellation
-
Trigger a scheduled pipeline from the Rails console (replace
GROUP_PATH):target_project = Project.find_by_full_path('GROUP_PATH/target-project') schedule = Security::PipelineExecutionProjectSchedule.find_by(project_id: target_project.id) Security::PipelineExecutionPolicies::RunScheduleWorker.new.perform(schedule.id) -
In
target-project, go to Build > Pipelines. The pipeline should be created, pending, or running. -
Merge the merge request that disables the policy.
-
Within a few seconds the pipeline should be cancelled — with no feature flag set anywhere.
Unlinking the project from the policy scope cancels the pipeline the same way. If you poll for the status from a rails runner script rather than the UI, wrap the reads in Ci::ApplicationRecord.uncached — ActiveRecord::Base.uncached does not reach the ci pool.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.



