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 " 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 }}! please review\nSee logs at https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"