DEV Community

Cover image for Feature Flags changed how we ship code, why not our pipelines too?
Lam Phan
Lam Phan

Posted on

Feature Flags changed how we ship code, why not our pipelines too?

Have you ever needed to ship a new job to your pipeline, e.g. a security scan, a performance benchmark, an integration test against a staging environment that's still flaky, while your ticket has been sitting In-Progress for two weeks? You know it won't stay optional forever, and the blockers will get resolved eventually. But right now, today, you need a way to land it without breaking everyone else's workflow.

This is the real problem especially in fast-paced teams

In a fast-paced team, code ships several times on a daily basis. But pipelines? Pipelines evolve cautiously, and for good reason. A misconfigured workflow doesn't just fail your build, it blocks the entire team. There's just a "Revert" option that can temporarily resolves it.

This creates a tension that every Platform and DevOps engineer knows intimately: you're supposed to ship new jobs/steps on the pipeline as fast as the product (maybe faster), but the cost of getting it wrong is disproportionately high.

Why Existing Solutions Fall Short

You might think this is already solved. It isn't, not cleanly.

Environment variables and repository secrets work, but they're global. Flipping a flag means flipping it for every commit, every PR, every contributor. That's not an experiment, that's a rollout.

Branch-based conditions (if: github.ref == 'refs/heads/experiment-branch') tie your pipeline logic to your Git topology. Now your branching strategy has to accommodate your CI experiments.

Manual workflow dispatches (workflow_dispatch) let you trigger jobs on demand, but they exist outside the normal development flow and by its name, it's not automated.

Separate workflow files for experimental pipelines create drift immediately. Now you have two workflow definitions to keep in sync.

None of these approaches give you what you actually want: the ability for a developer to opt into an experimental pipeline behavior at the granularity of a single commit or PR, with zero coordination overhead.

The Commit Message as a Control Plane

Your commit message and PR title already carry intent. Developers use conventional commits, ticket references, and prefixes constantly. Why not extend that pattern to pipeline control?

Using Flags in commit message of PR title allows producing boolean flags that downstream steps can consume. The implementation is simple: define your patterns, point the action at a commit message or PR title, and use the output flags for your need.

Want to run a heavy integration suite only when someone signals they're ready?

fix(auth): resolve token refresh race condition [run-integration]
Enter fullscreen mode Exit fullscreen mode

The [run-integration] tag gets parsed into a flag. A downstream job checks that flag. The integration suite runs — but only for this PR, only for this developer, only right now.

No secrets to toggle. No branch name hacks. No Slack message to the platform team asking them to flip a switch.

This extends the convention GitHub Actions already established with [skip ci], but instead of a single hardcoded behavior, you define your own flags for your own workflows.

Benefits

By extending the flags in commit message of Github Actions defaults ([skip-ci],...), it brings us a lot of advantages

Safely Introducing New Pipeline Steps

This is where things get practical. Say your team needs to add an ARM64 build target alongside your existing x86 builds. You know the new target will surface cross-platform issues, incompatible native dependencies, architecture-specific compiler flags, base images that don't have ARM variants yet. If you add it as a required job on day one, every PR in the repository is blocked until the entire dependency tree is ARM-compatible.

Auto Ephemeral Environments Deployment on Demand

Not every PR needs a live preview environment. Spinning one up takes time, costs money, and consumes shared infrastructure. Instead of burning a preview slot for every PR, including dependency bumps, config changes, and refactors that have no visible UI impact, you reserve them for the PRs that actually benefit. The developer makes the call, right in the PR title, with no extra tooling or coordination required.

Implementation Details

Doing It by Hand

jobs:
  parse-flags:
    runs-on: ubuntu-latest
    outputs:
      deploy-preview: ${{ steps.flags.outputs.deploy-preview }}
      integration-tests: ${{ steps.flags.outputs.integration-tests }}
    steps:
      - name: Parse flags from commit message
        id: flags
        run: |
          COMMIT_MSG="${{ github.event.head_commit.message }}"

          if echo "$COMMIT_MSG" | grep -q "\[deploy-preview\]"; then
            echo "deploy-preview=true" >> "$GITHUB_OUTPUT"
          else
            echo "deploy-preview=false" >> "$GITHUB_OUTPUT"
          fi

          if echo "$COMMIT_MSG" | grep -q "\[integration-tests\]"; then
            echo "integration-tests=true" >> "$GITHUB_OUTPUT"
          else
            echo "integration-tests=false" >> "$GITHUB_OUTPUT"
          fi
Enter fullscreen mode Exit fullscreen mode

This approach gives you the full control of managing your flags. But the problem is, two flags, and the parsing step is already 10+ lines of repetitive shell scripting. Add a third flag and you're copy-pasting another if/else block. Swap the input from commit message to PR title and you're rewriting the extraction logic. It works, but it scales poorly and it's easy to introduce a typo that silently breaks a flag.

Flag Parser

jobs:
  parse-flags:
    runs-on: ubuntu-latest
    outputs:
      deploy-preview: ${{ steps.flags.outputs.deploy-preview }}
      skip-tests: ${{ steps.flags.outputs.skip-tests }}
    steps:
      - name: Parse flags
        id: flags
        uses: platformcrew/flag-parser@v1
        with:
          flags: |
            deploy-preview: "[deploy-preview]"
            skip-tests: "[skip-tests]"
Enter fullscreen mode Exit fullscreen mode

Flag Parser helps you to reduce the boilerplate of parsing and managing those flags down to a single, declarative step, define your patterns once, swap to a PR title or PR body by setting the text input, and let every downstream job reference clean boolean outputs instead of scattering regex matches and conditional expressions across your workflow file.

Conclusion

Giving developers a low-friction, low-risk way to experiment with their pipeline, the same way we let end users experiment with our product via feature flags, changes the culture. Pipelines become something the team actively improves, not something they endure.

Your pipeline deserves the same iterative, experiment-driven development process as your product.

Flag Parser is open source and available on the GitHub Actions Marketplace.

Top comments (0)