Feature flag lifecycle: categorize, reduce, and elevate permanent configuration
## Problem
GitLab Runner carries **54 feature flags** ([`helpers/featureflags/flags.go`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go)). Most were added to roll something out. Flags do get removed — 72 have existed over the years and 17 are gone — but only ever one at a time, when someone happens to get to it. Every flag adds another branch through the code, which makes it harder to read, harder to test, and harder to support. Some flags depend on each other, and at least three no longer do anything (or nothing under Concrete dispatch).
The `ToBeRemovedWith` field exists in the flag definition and is rendered as a column in the generated docs table. **It is empty for all 54 flags.**
Four distinct problems hide behind the single `FF_` prefix:
1. **Rollouts that finished but were never cleaned up.** The new behavior became the default long ago. The flag is still there so users can switch back, and nobody has scheduled its removal.
2. **Settings that will never go away, dressed up as feature flags.** Things like entrypoint handling, umask behavior, init processes and network-per-build depend on the user's environment. There is no "right" default to settle on, so these flags will never be removed — but they also aren't documented or discoverable the way real configuration is.
3. **Rollouts that stalled.** Flags added switched off, where the evidence to switch them on never arrived. The oldest, `FF_USE_FASTZIP`, has been off for 5.8 years.
4. **Rollouts that failed and were left in place.** Two flags were switched on and then rolled back: `FF_USE_POWERSHELL_PATH_RESOLVER` lasted a single day, and `FF_GIT_URLS_WITHOUT_TOKENS` was reverted several times across 2024–2025. Both look like live rollouts, but really they are decisions nobody has made.
Two numbers show how big (1) and (3) are. Of the 54 flags, **only 7 have ever had their default changed on purpose**. Another **9 started life as `true`** — they were never rollouts at all, just a switch for users who need the old behavior.
## Goal
Establish an ongoing workstream that:
- Removes flags whose rollout completed.
- Turns the flags that are really settings into documented `config.toml` options, with a deprecation path for the `FF_` name.
- Forces a flip-or-kill decision on each stalled rollout.
- Institutes a lifecycle policy so the flag count stops growing unbounded.
Success is that every remaining flag is either an active rollout with an owner and a removal milestone, or has been reclassified as documented configuration.
## Current inventory (54 flags)
| Category | Count | Disposition |
|---|---|---|
| Deprecated / does nothing | 1 | Remove now |
| Legacy switch-backs | 19 | Remove a few per milestone, each with a deprecation announcement |
| Permanent configuration | 14 | Turn into documented `config.toml` settings; deprecate the `FF_` name |
| Off by default — needs a decision | 16 | Decide per flag: switch on and remove, or accept as configuration. |
| Active strategic rollouts | 4 | Keep — doing their job (job router, steps migration, suspendable environments) |
In the tables below, **Added** is the first release that contained the flag, linked to the MR that added it.
**Default since** is the date the flag's *current* default took effect, and how long ago that was:
- *never changed* — the flag has had this default since the day it was added.
- *changed* — someone changed the default later, on that date. The date links to the MR that changed it.
For the legacy switch-backs, this is the number that should decide what gets removed first. What matters is how long the default has been correct, not how long the flag has existed.
**Status** tracks each flag through the workflow below — update your row when you pick a flag up and again when you close your issue:
| | Meaning |
|---|---|
| ⬜ | Not addressed — needs an owner |
| 🚧 | Work already underway in a linked issue — nobody needs to pick this up |
| ✅ | Done — decision recorded and acted on, issue closed |
| ➖ | Out of scope for this epic |
#### Deprecated / does nothing (1)
Delete it. The flag is documented as doing nothing, and nothing outside tests reads it.
| Status | Flag | Added | Default | Default since | Open issues | Notes |
|---|---|---|---|---|---|---|
| ✅ https://gitlab.com/gitlab-org/gitlab-runner/-/work_items/39679+ | [`FF_USE_NEW_BASH_EVAL_STRATEGY`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L18) | [v13.12.0 (2021-04-13)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2818) | `false` | 2021-04-13 · 5.3y *never changed* | [#39679 (19.3)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39679), [#27911 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27911), [#27910 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27910), [#27909 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27909), [#27908 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27908), [#27907 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27907), [#27906 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27906) | Scheduled in [#39679](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39679) (19.3). Removal should also close the six obsolete Backlog rollout issues listed alongside. |
#### Legacy switch-backs — the default is already right (19)
These exist only so users can switch back to the old behavior. Remove them a few per milestone, each with a deprecation announcement. The MR must delete the old code path too, not just the flag. **Work through them in *Default since* order, not *Added* order** — what matters is how long the default has been right, not how long the flag has existed.
| Status | Flag | Added | Default | Default since | Open issues | Notes |
|---|---|---|---|---|---|---|
| ✅ https://gitlab.com/gitlab-org/gitlab-runner/-/work_items/39679+ | [`FF_SKIP_NOOP_BUILD_STAGES`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L13) | [v13.1.0 (2020-05-18)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2081) | `true` | 2020-05-18 · 6.2y *never changed* | [#39679 (19.3)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39679) | In [#39679](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39679) (19.3). |
| ✅ https://gitlab.com/gitlab-org/gitlab-runner/-/work_items/39679+ | [`FF_SET_PERMISSIONS_BEFORE_CLEANUP`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L30) | [v15.10.0 (2023-03-09)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3726) | `true` | 2023-03-09 · 3.4y *never changed* | [#39679 (19.3)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39679) | In [#39679](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39679) (19.3). |
| ⬜ | [`FF_USE_LEGACY_KUBERNETES_EXECUTION_STRATEGY`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L11) | [v12.10.0 (2020-03-10)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/1775) | `false` | [2021-06-14 · 5.2y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2955) *changed* | — | Oldest flag in the registry; default correct for 5.2 years. |
| ⬜ | [`FF_USE_DIRECT_DOWNLOAD`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L12) | [v13.1.0 (2020-05-14)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2093) | `true` | [2020-05-20 · 6.2y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2115) *changed* | — | ⚠️ Read through the struct tag `env:"FF_USE_DIRECT_DOWNLOAD"` at [`commands/helpers/artifacts_downloader.go:28`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/commands/helpers/artifacts_downloader.go#L28), **not** the `featureflags` const — see the removal caveat below. |
| ⬜ | [`FF_USE_WINDOWS_LEGACY_PROCESS_STRATEGY`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L17) | [v13.10.0 (2021-03-08)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2739) | `false` | [2024-02-26 · 2.5y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4659) *changed* | — | — |
| ⬜ | [`FF_RESOLVE_FULL_TLS_CHAIN`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L26) | [v15.6.0 (2022-11-07)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3699) | `false` | [2023-09-21 · 2.9y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4292) *changed* | — | Can only be disabled via `[runners.feature_flags]`, not as a job variable. |
| ⬜ | [`FF_USE_POD_ACTIVE_DEADLINE_SECONDS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L28) | [v15.10.0 (2023-02-16)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3897) | `true` | [2023-10-12 · 2.8y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4361) *changed* | [#36809](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/36809) | — |
| ⬜ | [`FF_USE_GIT_BUNDLE_URIS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L33) | [v16.6.0 (2023-10-24)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4418) | `true` | 2023-10-24 · 2.8y *never changed* | — | — |
| ⬜ | [`FF_USE_DOCKER_AUTOSCALER_DIAL_STDIO`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L38) | [v16.7.0 (2023-12-16)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4509) | `true` | 2023-12-16 · 2.7y *never changed* | — | — |
| ⬜ | [`FF_SECRET_RESOLVING_FAILS_IF_MISSING`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L31) | [v16.0.0 (2023-05-17)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4064) | `true` | 2023-05-17 · 3.2y *never changed* | — | — |
| ⬜ | [`FF_USE_EXPONENTIAL_BACKOFF_STAGE_RETRY`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L51) | [v18.0.0 (2025-05-09)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4517) | `true` | 2025-05-09 · 1.3y *never changed* | — | — |
| ⬜ | [`FF_USE_LEGACY_GCS_CACHE_ADAPTER`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L43) | [v17.0.0 (2024-04-30)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4706) | `false` | 2024-04-30 · 2.3y *never changed* | — | — |
| ⬜ | [`FF_USE_LEGACY_S3_CACHE_ADAPTER`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L45) | [v17.5.0 (2024-09-16)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4987) | `false` | 2024-09-16 · 1.9y *never changed* | — | — |
| ⬜ | [`FF_MASK_ALL_DEFAULT_TOKENS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L48) | [v17.9.0 (2025-02-09)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4853) | `true` | 2025-02-09 · 1.5y *never changed* | — | — |
| ⬜ | [`FF_USE_ADAPTIVE_REQUEST_CONCURRENCY`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L52) | [v18.1.0 (2025-05-09)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5546) | `true` | 2025-05-09 · 1.3y *never changed* | [#39575](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39575) | Open defect [#39575](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39575) against the default-on behavior — resolve before removing. |
| ⬜ | [`FF_USE_GITALY_CORRELATION_ID`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L53) | [v18.2.0 (2025-06-18)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5653) | `true` | 2025-06-18 · 1.1y *never changed* | — | — |
| ⬜ | [`FF_TIMESTAMPS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L41) | [v16.11.0 (2024-04-09)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4591) | `true` | [2025-11-28 · 0.7y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5861) *changed* | — | **Wait** — the default only changed in 2025-11. Let it run another release or two before scheduling removal. |
| ⬜ | [`FF_ENABLE_JOB_INPUTS_INTERPOLATION`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L56) | [v18.7.0 (2025-12-02)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5855) | `true` | [2026-02-04 · 0.5y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6275) *changed* | — | **Wait** — the default changed in 2026-02, after being switched on, reverted, and switched on again. Watch it for a while before scheduling removal. |
| ⬜ | [`FF_USE_LEGACY_BASH_EVAL`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L19) | [v19.1.0 (2026-06-08)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6784) | `false` | 2026-06-08 · 0.2y *never changed* | — | **Wait** — only added in 2026-06, as the replacement switch-back for the deprecated flag above. |
#### Permanent configuration (14)
Turn these into proper, documented `config.toml` settings (or job-level settings where that fits better). The `FF_` name keeps working as a deprecated alias for a while, and we drop the alias at the next major release.
| Status | Flag | Added | Default | Default since | Open issues | Notes |
|---|---|---|---|---|---|---|
| ⬜ | [`FF_NETWORK_PER_BUILD`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L10) | [v12.9.0 (2020-02-25)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/1569) | `false` | 2020-02-25 · 6.5y *never changed* | [#39382](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39382), [#39145 (19.4)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39145), [#38979](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/38979), [#38794](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/38794), [#27686 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27686) | Five open issues, incl. [#38979](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/38979) asking for configurable network options — a concrete example of why this belongs in `config.toml`. |
| ⬜ | [`FF_KUBERNETES_HONOR_ENTRYPOINT`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L24) | [v14.5.0 (2021-10-21)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3095) | `false` | 2021-10-21 · 4.8y *never changed* | [#39470](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39470), [#37612](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/37612) | Behavior is conditional on `FF_USE_LEGACY_KUBERNETES_EXECUTION_STRATEGY`; the config design must resolve that coupling. |
| ⬜ | [`FF_POSIXLY_CORRECT_ESCAPES`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L25) | [v14.9.0 (2022-03-18)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3367) | `false` | 2022-03-18 · 4.4y *never changed* | [#39670](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39670) | [#39670](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39670): not honored under Concrete. |
| ⬜ | [`FF_DISABLE_POWERSHELL_STDIN`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L27) | [v15.6.0 (2022-11-17)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3728) | `false` | 2022-11-17 · 3.7y *never changed* | — | — |
| ⬜ | [`FF_USE_ADVANCED_POD_SPEC_CONFIGURATION`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L29) | [v15.11.0 (2023-03-17)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3114) | `false` | 2023-03-17 · 3.4y *never changed* | — | Already gates a `config.toml` block and is documented as an Experiment — the work is graduating it, not designing it. |
| ⬜ | [`FF_DISABLE_UMASK_FOR_KUBERNETES_EXECUTOR`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L44) | [v17.2.0 (2024-07-08)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4842) | `false` | 2024-07-08 · 2.1y *never changed* | [#39540](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39540) | [#39540](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39540): no-op under Concrete dispatch. |
| ⬜ | [`FF_LOG_IMAGES_CONFIGURED_FOR_JOB`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L37) | [v16.7.0 (2023-11-25)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4499) | `false` | 2023-11-25 · 2.7y *never changed* | — | — |
| ⬜ | [`FF_USE_DUMB_INIT_WITH_KUBERNETES_EXECUTOR`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L35) | [v16.6.0 (2023-11-13)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4443) | `false` | 2023-11-13 · 2.7y *never changed* | — | — |
| ⬜ | [`FF_USE_INIT_WITH_DOCKER_EXECUTOR`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L36) | [v16.6.1 (2023-11-23)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4488) | `false` | 2023-11-23 · 2.7y *never changed* | — | — |
| ⬜ | [`FF_USE_NATIVE_CONTAINER_STOP`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L63) | [v19.1.0 (2026-06-05)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6636) | `false` | 2026-06-05 · 0.2y *never changed* | — | — |
| ⬜ | [`FF_DISABLE_AUTOMATIC_TOKEN_ROTATION`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L42) | [v17.0.0 (2024-04-19)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4721) | `false` | 2024-04-19 · 2.3y *never changed* | — | — |
| ⬜ | [`FF_EXPORT_HIGH_CARDINALITY_METRICS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L49) | [v17.9.0 (2025-02-13)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5334) | `false` | 2025-02-13 · 1.5y *never changed* | — | — |
| ⬜ | [`FF_ENABLE_JOB_CLEANUP`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L23) | [v14.3.0 (2021-09-07)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3065) | `false` | 2021-09-07 · 4.9y *never changed* | [#28772](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28772), [#28152](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28152) | — |
| ⬜ | [`FF_WAIT_FOR_POD_TO_BE_REACHABLE`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L47) | [v17.6.0 (2024-10-21)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5098) | `false` | 2024-10-21 · 1.8y *never changed* | — | — |
#### Off by default — needs a decision (16)
Each one needs a decision: either switch the default on and then remove the flag, or accept it as configuration and move it to the group above. Nothing stays undecided.
Most have been off for years. Two were switched on and then reverted, which is the clearest sign the decision was put off rather than made, and three have no tracking issue at all. The last three were added in the past two releases — for those, "not yet, recheck later" is a legitimate verdict. Record it in your issue and move on.
| Status | Flag | Added | Default | Default since | Open issues | Notes |
|---|---|---|---|---|---|---|
| 🚧 https://gitlab.com/gitlab-org/gitlab-runner/-/work_items/39681+ | [`FF_USE_FASTZIP`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L14) | [v13.6.0 (2020-11-04)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2210) | `false` | 2020-11-04 · 5.8y *never changed* | — | Off by default for **5.8 years**. No tracking issue. |
| ⬜ | [`FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L15) | [v13.7.0 (2020-11-30)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2539) | `false` | 2020-11-30 · 5.7y *never changed* | [#37304 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/37304), [#30949](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/30949) | 🔄 *Reclassified from Permanent configuration* — [#37304](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/37304) is open asking to switch it on by default, so a rollout was intended. |
| ⬜ | [`FF_ENABLE_BASH_EXIT_CODE_CHECK`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L16) | [v13.9.0 (2021-01-13)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2671) | `false` | 2021-01-13 · 5.6y *never changed* | [#39634](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39634) | Open correctness defect [#39634](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39634). |
| ⬜ | [`FF_USE_POWERSHELL_PATH_RESOLVER`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L20) | [v13.12.0 (2021-05-05)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/2836) | `false` | 2021-05-05 · 5.3y *never changed* | [#29683 (Backlog)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29683) | ❌ **Failed rollout**: switched on and reverted the same day, 2023-03-06. [#29683](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/29683) has been open asking to switch it on since 2021. |
| ⬜ | [`FF_SCRIPT_SECTIONS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L22) | [v14.2.0 (2021-08-09)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3051) | `false` | 2021-08-09 · 5.0y *never changed* | [#39294](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39294), [#37312](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/37312), [#28132](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28132), [#28115](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28115) | 🔄 *Reclassified from Permanent configuration* — has an open rollout issue ([#28132](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28132)) **and** an open removal issue ([#28115](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28115)), plus [#39294](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39294) on breaking changes. An unfinished rollout, not configuration. |
| ⬜ | [`FF_USE_DYNAMIC_TRACE_FORCE_SEND_INTERVAL`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L21) | [v14.2.0 (2021-08-06)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/3064) | `false` | 2021-08-06 · 5.0y *never changed* | — | No tracking issue at all. Strongest candidate for removal. |
| ⬜ | [`FF_PRINT_POD_EVENTS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L32) | [v16.5.0 (2023-10-19)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4420) | `false` | 2023-10-19 · 2.8y *never changed* | [#37457](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/37457) | 🔄 *Reclassified from Permanent configuration* — [#37457](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/37457) is open asking to switch it on by default. |
| ⬜ | [`FF_CLEAN_UP_FAILED_CACHE_EXTRACT`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L39) | [v16.8.0 (2024-01-09)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4565) | `false` | 2024-01-09 · 2.6y *never changed* | [#38792](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/38792) | Open defect [#38792](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/38792). |
| ⬜ | [`FF_USE_WINDOWS_JOB_OBJECT`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L40) | [v16.9.0 (2024-01-26)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4525) | `false` | 2024-01-26 · 2.5y *never changed* | — | No tracking issue. |
| ⬜ | [`FF_GIT_URLS_WITHOUT_TOKENS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L46) | [v17.5.0 (2024-10-11)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5031) | `false` | [2025-05-16 · 1.2y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5572) *changed* | [#39617](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39617) | ❌ **Failed rollout, repeatedly**: shipped switched on, then off a week later, then switched on and off twice more in May 2025. Open regression [#39617](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39617). |
| ⬜ | [`FF_USE_FLEETING_ACQUIRE_HEARTBEATS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L50) | [v17.10.0 (2025-02-19)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5340) | `false` | 2025-02-19 · 1.5y *never changed* | — | No tracking issue. |
| ⬜ | [`FF_USE_GIT_NATIVE_CLONE`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L34) | [v18.0.0 (2025-04-25)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5010) | `false` | 2025-04-25 · 1.3y *never changed* | [#39641](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39641) | Open correctness defect [#39641](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39641) (clones the mutable MR merge ref) — concrete evidence against flipping. |
| 🚧 | [`FF_HASH_CACHE_KEYS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L55) | [v18.4.0 (2025-08-13)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5751) | `false` | [2025-08-14 · 1.0y](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5751) *changed* | [#39561 (19.4)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39561), [#39560 (19.4)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39560), [#39453 (19.4)](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39453) | **Already decided** — being switched on and removed in 19.4: [#39561](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39561) sets the default to `true`, [#39560](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39560) enforces it globally, [#39453](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39453) removes the flag and its fallback logic. Already enforced on GitLab.com via chef-repo. Nothing to pick up here. |
| ⬜ | [`FF_USE_GIT_PROACTIVE_AUTH`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L54) | [v18.10.0 (2026-02-24)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6444) | `false` | 2026-02-24 · 0.5y *never changed* | — | **Too new to judge** — added five months ago. "Not yet, recheck later" is a valid verdict. |
| ⬜ | [`FF_USE_PARALLEL_CACHE_TRANSFER`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L59) | [v19.0.0 (2026-04-16)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6401) | `false` | 2026-04-16 · 0.3y *never changed* | — | **Too new to judge** — added four months ago. "Not yet, recheck later" is a valid verdict. |
| ⬜ | [`FF_USE_PARALLEL_ARTIFACT_TRANSFER`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L60) | [v19.0.0 (2026-04-16)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6401) | `false` | 2026-04-16 · 0.3y *never changed* | — | **Too new to judge** — added four months ago. "Not yet, recheck later" is a valid verdict. |
#### Active strategic rollouts (4)
Keep them. These are doing their job. When to roll them out is decided in their own epics, not here.
| Status | Flag | Added | Default | Default since | Open issues | Notes |
|---|---|---|---|---|---|---|
| ➖ | [`FF_USE_JOB_ROUTER`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L57) | [v18.8.0 (2025-12-16)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5945) | `false` | 2025-12-16 · 0.6y *never changed* | — | Job Router. |
| ➖ | [`FF_SCRIPT_TO_STEP_MIGRATION`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L58) | [v18.9.0 (2026-01-29)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6069) | `false` | 2026-01-29 · 0.5y *never changed* | — | Steps migration. |
| ➖ | [`FF_CONCRETE`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L61) | [v18.11.0 (2026-03-17)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6410) | `false` | 2026-03-17 · 0.4y *never changed* | [#39668](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39668) | Steps migration. [#39668](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/39668) proposes making it the default execution path. |
| ➖ | [`FF_SUSPENDABLE_ENVIRONMENTS`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go#L62) | [v19.0.0 (2026-05-07)](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/6570) | `false` | 2026-05-07 · 0.3y *never changed* | — | Suspendable environments. |
## Proposed approach
**Each team member is assigned one feature flag per milestone.**
### When you are assigned a flag
1. **Create an issue for yourself in the gitlab-runner project** to manage that flag, as a child of this epic, using the template below. Update the epic description with the issue number in the relevant row.
2. **Verify the categorization.** Check the codebase, the open issues, and the introducing MR (linked from the *Added* column). Look for failed rollouts in the flag's history, and reach out to the original author to understand why it was never rolled out. If the author has left the project, reconstruct the intent from the MR discussion rather than blocking on a reply.
3. **Record the verdict in your issue either way** — including "categorization confirmed, no change". A recorded no-change verdict is what stops the next person from redoing your research. If the categorization was wrong, correct that row in the inventory table above.
4. **Act on the verdict.** One of three outcomes:
- **Remove** — author the MRs. Delete the dead code path and the docs entry, not just the flag definition.
- **Permanent configuration** — author the MRs elevating it to a documented `config.toml` setting, with the `FF_` form kept as a deprecated alias.
- **Keep as an active rollout** — set a real `ToBeRemovedWith` target, link a rollout issue, and put a name on it as owner. "Keep" is a valid verdict, but it is not a way to defer the decision again.
5. **Close the issue when complete.**
### Per-flag issue template
<details>
<summary>Use these fields, so that 50+ issues stay comparable and the reasoning outlives the people who wrote it</summary>
**Title**: Remove FF_INSERT_NAME_HERE
**Description**:
````
- **Flag** and current category
- **Introduced by** — MR, author, release
- **Flip history** — including any failed or reverted flips
- **Open issues** referencing the flag
- **Author contacted?** — yes/no, and what they said
- **Verdict** — confirmed / recategorised to X, with the evidence
- **Action** — MR links, or why no action is needed
> ⚠️ **Removal caveat.** Not every flag is read through the `featureflags` package. `FF_USE_DIRECT_DOWNLOAD` is consumed via a struct tag (`env:"FF_USE_DIRECT_DOWNLOAD"` at [`commands/helpers/artifacts_downloader.go:28`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/commands/helpers/artifacts_downloader.go#L28)), so the `featureflags.UseDirectDownload` constant has **no production references at all**. A removal MR that greps for the Go identifier will conclude the flag is dead code and silently break direct download. Every removal must grep for the literal `FF_` string.
> 📝 **Editing this epic's table.** The inventory is generated from [`helpers/featureflags/flags.go`](https://gitlab.com/gitlab-org/gitlab-runner/-/blob/main/helpers/featureflags/flags.go) plus the GitLab API. GitLab descriptions are last-write-wins with no merge, so two people editing the table in the same window will silently clobber each other. Keep table edits to the single row you own, and treat your issue — not this description — as the authoritative record of your verdict.
/assign me
/label ~"type::maintenance" ~"maintenance::release" ~"Category:Runner Core"
/set_parent https://gitlab.com/groups/gitlab-org/-/work_items/23127
````
</details>
## Out of scope
- Rollout decisions for the active strategic flags (job router, steps migration, suspendable environments) — those belong to their respective epics.
- Changes to how feature flags are evaluated at runtime.
epic