Skip to content

Feature Flag Cleanup #2

Feature Flag Cleanup

Feature Flag Cleanup #2

name: Feature Flag Cleanup
on:
schedule:
- cron: '0 15 * * *' # 10am EST
workflow_dispatch:
# Default to a read-only token. Jobs that need to push or open PRs widen
# permissions explicitly below.
permissions:
contents: read
jobs:
analyze:
name: Analyze feature flags
runs-on: namespace-profile-ubuntu-small
permissions:
contents: read
pull-requests: read
outputs:
flag_name: ${{ steps.select_flag.outputs.flag_name }}
cargo_flag: ${{ steps.select_flag.outputs.cargo_flag }}
reviewers: ${{ steps.select_flag.outputs.reviewers }}
analyzed_sha: ${{ steps.record_sha.outputs.analyzed_sha }}
artifact_url: ${{ steps.upload_feature_flag_log.outputs.artifact-url }}
steps:
- name: Check out code
uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
with:
fetch-depth: 0
- name: Record analyzed commit
id: record_sha
run: |
echo "analyzed_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Find old feature flags
uses: warpdotdev/oz-agent-action@main
with:
prompt: |
Your task is to find and list any feature flags which have been enabled by default for more than 3 months.
There will always be at least one such flag.
## Context
Feature flags are defined in warp_core/src/features.rs. They're enabled by default in app/Cargo.toml, using the default feature list. The mapping between Cargo features and app features is in the enabled_features function of app/src/lib.rs.
## Recommended Procedure
1. Use `git blame` on the Cargo.toml file to identify when each feature flag was enabled by default.
2. Read the enabled_features function to map Cargo features to app features.
3. Write features that have been enabled by default for more than 3 months to the output file.
You may create temporary files to store useful results, and write helper scripts for subtasks. It is not necessary to do everything in one shot.
## Result Format
List the old feature flags in ${{ runner.temp }}/flags_to_cleanup.jsonl, in this format (one flag per line):
{ "flag_name": "MyFlag", "cargo_flag": "my_flag", "date_enabled": "2025-02-21", "enabling_commit": "aad3293b8fe" }
## Reminders
* Fields in the output JSONL file have these meanings:
* "flag_name" - the name of the FeatureFlag enum variant
* "cargo_flag" - the name of the flag used in Cargo.toml
* "date_enabled" - the date on which the feature flag was enabled by default
* "enabling_commit" - the Git commit in which the feature flag was enabled by default
* You are running as part of a GitHub automation and must not commit or push any changes. Another agent will process the file you produce.
warp_api_key: ${{ secrets.WARP_API_KEY }}
share: team
- name: Upload feature flag log
id: upload_feature_flag_log
uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3
with:
name: flags-to-cleanup
path: ${{ runner.temp }}/flags_to_cleanup.jsonl
# As we test out the workflow, only clean up one flag at a time.
# We could also pick several flags, or fan out jobs for every single one.
- name: Select a flag to clean up
id: select_flag
env:
GH_TOKEN: ${{ github.token }}
run: |
cleanup_log="$RUNNER_TEMP/flags_to_cleanup.jsonl"
# Skip over flags that are also used to enable/disable functionality at runtime.
excluded_flags='["CreatingSharedSessions", "ViewingSharedSessions", "AgentMode"]'
flag_name=""
cargo_flag=""
# Iterate flags in order (oldest first) and pick the first one that is not
# excluded and does not already have an open cleanup PR. Sort by
# date_enabled here rather than relying on the analysis agent to emit
# the JSONL in any particular order.
while IFS= read -r candidate; do
[ -z "$candidate" ] && continue
candidate_flag="$(jq -r '.flag_name' <<< "$candidate")"
if [ "$(jq -n --arg flag "$candidate_flag" --argjson excluded "$excluded_flags" '$excluded | index($flag) != null')" = "true" ]; then
echo "Skipping $candidate_flag: in excluded list"
continue
fi
candidate_branch="oz-agent/cleanup-feature-flag-$candidate_flag"
existing_pr="$(gh pr list --state open --head "$candidate_branch" --json number --jq '.[0].number')"
if [ -n "$existing_pr" ] && [ "$existing_pr" != "null" ]; then
echo "Skipping $candidate_flag: open cleanup PR #$existing_pr already exists on $candidate_branch"
continue
fi
flag_name="$candidate_flag"
cargo_flag="$(jq -r '.cargo_flag' <<< "$candidate")"
break
done < <(jq -c -s 'sort_by(.date_enabled) | .[]' "$cleanup_log")
echo "flag_name=$flag_name" >> "$GITHUB_OUTPUT"
echo "cargo_flag=$cargo_flag" >> "$GITHUB_OUTPUT"
echo "Cleaning up $flag_name ($cargo_flag)"
if [ -z "$flag_name" ] || [ "$flag_name" = "null" ]; then
echo "No flag selected for cleanup"
echo "reviewers=" >> "$GITHUB_OUTPUT"
else
enabling_commit="$(jq -r -cs --arg flag "$flag_name" '.[] | select(.flag_name == $flag) | .enabling_commit' "$cleanup_log")"
committer_username=""
# Resolve the GitHub username of the commit author directly via the
# commits API. This is more reliable than mapping a commit email to
# a GitHub user, since commit emails are often noreply addresses or
# otherwise unsearchable.
if [ -n "$enabling_commit" ] && [ "$enabling_commit" != "null" ]; then
committer_username="$(gh api "repos/${{ github.repository }}/commits/$enabling_commit" --jq '.author.login // ""' 2>/dev/null || echo "")"
fi
default_reviewers="bennavetta"
# Verify the committer is still a collaborator before assigning as reviewer.
if [ -n "$committer_username" ] && gh api "repos/${{ github.repository }}/collaborators/$committer_username" --verbose 2>/dev/null; then
echo "Flag originally enabled by $committer_username"
echo "reviewers=$committer_username,$default_reviewers" >> "$GITHUB_OUTPUT"
elif [ -n "$committer_username" ]; then
echo "$committer_username is no longer a collaborator, using fallback"
echo "reviewers=$default_reviewers" >> "$GITHUB_OUTPUT"
else
echo "Could not determine flag committer, using fallback"
echo "reviewers=$default_reviewers" >> "$GITHUB_OUTPUT"
fi
fi
cleanup:
name: Run cleanup agent
needs: analyze
if: ${{ needs.analyze.outputs.flag_name && needs.analyze.outputs.flag_name != 'null' }}
runs-on: namespace-profile-ubuntu-small
permissions:
contents: read
steps:
- name: Check out code
uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
with:
ref: ${{ needs.analyze.outputs.analyzed_sha }}
fetch-depth: 0
- name: Setup Rust cache
uses: namespacelabs/nscloud-cache-action@a90bb5d4b27522ce881c6e98eebd7d7e6d1653f9 # v1.4.2
with:
cache: rust
- name: Clean up the unused feature flag
id: cleanup_agent
uses: warpdotdev/oz-agent-action@main
with:
prompt: |
The feature flag ${{ needs.analyze.outputs.flag_name }} is now enabled by default.
Clean up all references to this flag, modifying code to always assume it is enabled. Ensure that the code is correctly formatted and that tests still pass.
Finally, remove the flag from the `FeatureFlag` enum, and remove the equivalent ${{ needs.analyze.outputs.cargo_flag }} feature in app/Cargo.toml
You are running as part of a GitHub automation that runs with a read-only token and will package your changes into a patch for a separate job to commit. Do not create a branch, do not commit, do not push, do not create a PR, and do not call `gh`. Leave your changes in the working tree only.
share: team
warp_api_key: ${{ secrets.WARP_API_KEY }}
- name: Generate cleanup patch
env:
ANALYZED_SHA: ${{ needs.analyze.outputs.analyzed_sha }}
run: |
# If the cleanup agent made any local commits, undo them while keeping
# the changes in the working tree, so the resulting patch reflects the
# full set of changes relative to the analyzed commit.
git reset --mixed "$ANALYZED_SHA"
# Stage all changes (modified, new, deleted) so the patch is complete.
git add -A
git diff --staged --binary > "$RUNNER_TEMP/cleanup.patch"
if [ ! -s "$RUNNER_TEMP/cleanup.patch" ]; then
echo "Cleanup agent produced no changes" >&2
exit 1
fi
- name: Upload cleanup patch
uses: namespace-actions/upload-artifact@f6ccaacc655aec41b93af180d1d7eef21af862d2 # v1.0.3
with:
name: cleanup-patch
path: ${{ runner.temp }}/cleanup.patch
create_pr:
name: Create cleanup PR
needs: [analyze, cleanup]
if: ${{ needs.analyze.outputs.flag_name && needs.analyze.outputs.flag_name != 'null' }}
runs-on: namespace-profile-ubuntu-small
permissions:
contents: write
pull-requests: write
steps:
- name: Check out code
uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
with:
ref: ${{ needs.analyze.outputs.analyzed_sha }}
fetch-depth: 0
- name: Download cleanup patch
uses: namespace-actions/download-artifact@7cbad919e4b0e09f17e9d6311a444ff002992b5b # v2.0.1
with:
name: cleanup-patch
path: ${{ runner.temp }}
- name: Apply cleanup patch
run: |
git apply --binary --whitespace=nowarn "$RUNNER_TEMP/cleanup.patch"
- name: Create Pull Request
id: create_pr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ github.token }}
base: ${{ github.event.repository.default_branch }}
commit-message: "Clean up ${{ needs.analyze.outputs.flag_name }} feature flag"
branch: "oz-agent/cleanup-feature-flag-${{ needs.analyze.outputs.flag_name }}"
author: "Oz Agent <oz-agent@warp.dev>"
delete-branch: true
reviewers: ${{ needs.analyze.outputs.reviewers }}
title: "Clean up ${{ needs.analyze.outputs.flag_name }} feature flag"
body: |
Automated cleanup of the `${{ needs.analyze.outputs.flag_name }}` feature flag.
This PR was generated by the feature flag cleanup workflow.
- run: |
echo "Full feature flag analysis results: ${{ needs.analyze.outputs.artifact_url }}"
echo "Pull request created: ${{ steps.create_pr.outputs.pull-request-url }}"
- name: Send Slack notification
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
if: ${{ steps.create_pr.outputs.pull-request-operation == 'created' }}
with:
method: chat.postMessage
token: ${{ secrets.ACTION_MONITORING_SLACK }}
payload: |
channel: "#oncall-client"
# Follows https://api.slack.com/reference/surfaces/formatting#mentioning-users.
# S09SSGE729E is the user group ID for @eng-warp-3.
text: "🧹 New feature flag cleanup PR for `${{ needs.analyze.outputs.flag_name }}`: ${{ steps.create_pr.outputs.pull-request-url }}! <!subteam^S09SSGE729E> please review\nSee logs at https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"