Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/actions/feature-flag-drift-report/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Feature Flag Drift Report
description: 'Sends a Slack notification when feature flag drift is detected in E2E tests vs production.'

inputs:
title:
description: 'Project identifier shown in the message (e.g. MetaMask Extension, MetaMask Mobile)'
required: true
slack-webhook:
description: 'Slack incoming webhook URL'
required: true
workflow-run-url:
description: 'Full URL to the workflow run'
required: true
pr-url:
description: 'URL of the sync PR (optional; included in message when provided)'
required: false
default: ''

runs:
using: composite
steps:
- name: Build Slack payload
id: payload
shell: bash
env:
TITLE: ${{ inputs.title }}
WORKFLOW_URL: ${{ inputs.workflow-run-url }}
PR_URL: ${{ inputs.pr-url }}
run: |
BASE="*[${TITLE}] Feature Flags Drift Detected in E2E tests vs Prod* :warning:\n\nCheck the workflow run for details: download the drift report JSON artifact and review the report.\n\nYou can run command \`yarn feature-flags:sync:update\` locally to update the registry.\n\n"
if [[ -n "$PR_URL" ]]; then
MSG="${BASE}A sync PR has been created: <${PR_URL}|View PR>\n\n<${WORKFLOW_URL}|View workflow run>"
else
MSG="${BASE}<${WORKFLOW_URL}|View workflow run>"
fi
PAYLOAD=$(jq -n --arg m "$MSG" '{text: $m, blocks: [{type: "section", text: {type: "mrkdwn", text: $m}}]}')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slack message shows literal \n instead of newlines

Low Severity

The \n sequences in the BASE and MSG bash variables are literal two-character strings (backslash + n), not actual newlines. When passed to jq --arg, each backslash is JSON-escaped to \\, producing \\n in the JSON output. After Slack's JSON parser decodes this, the text contains literal \n characters. Slack's mrkdwn format does not interpret these as line breaks, so the entire message renders as a single unformatted block.

Fix in Cursor Fix in Web

{
echo "payload<<EOF"
printf '%s\n' "$PAYLOAD"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Send Slack notification
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a
with:
webhook: ${{ inputs.slack-webhook }}
webhook-type: incoming-webhook
payload: ${{ steps.payload.outputs.payload }}
143 changes: 143 additions & 0 deletions .github/workflows/check-feature-flag-registry-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: Create PR for Feature Flag Registry Drift

on:
workflow_call:
inputs:
repository:
required: true
type: string
description: 'The repository name (e.g. metamask-extension)'
registry-file-path:
required: true
type: string
description: 'Path in the repo where the registry file lives (e.g. app/scripts/feature-flags/registry.json)'
registry-artifact-name:
required: true
type: string
description: 'Name of the artifact containing the updated registry file'
report-artifact-name:
required: true
type: string
description: 'Name of the artifact containing report.json'
pr-label:
required: false
type: string
default: ''
description: 'Label to apply to the created PR (must exist in the target repo)'
secrets:
github-token:
required: true
description: 'Token with contents write and pull-requests write permissions'
outputs:
pr-url:
description: 'URL of the created pull request (empty if no PR was created)'
value: ${{ jobs.create-drift-pr.outputs.pr-url }}
has-changes:
description: 'Whether the registry had drift that needed syncing'
value: ${{ jobs.create-drift-pr.outputs.has-changes }}

jobs:
create-drift-pr:
outputs:
pr-url: ${{ steps.create-pr.outputs.pr-url }}
has-changes: ${{ steps.commit.outputs.has_changes }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
token: ${{ secrets.github-token }}

- name: Download registry artifact
uses: actions/download-artifact@v7
with:
name: ${{ inputs.registry-artifact-name }}
path: ${{ inputs.registry-artifact-name }}

- name: Download report artifact
uses: actions/download-artifact@v7
with:
name: ${{ inputs.report-artifact-name }}
path: ${{ inputs.report-artifact-name }}

- name: Build PR body from report
id: pr-body
env:
REPORT_ARTIFACT: ${{ inputs.report-artifact-name }}
run: |
REPORT_FILE="$REPORT_ARTIFACT/report.json"
if [[ ! -f "$REPORT_FILE" ]]; then
REPORT_FILE=$(find "$REPORT_ARTIFACT" -name "report.json" -type f | head -1)
fi
if [[ -z "$REPORT_FILE" || ! -f "$REPORT_FILE" ]]; then
echo "::error::report.json not found in artifact $REPORT_ARTIFACT"
exit 1
fi
{
echo "## Feature Flag Registry Drift Report"
echo ""
echo "The following drift was detected between E2E tests and production:"
echo ""
echo '```json'
cat "$REPORT_FILE"
echo '```'
} > pr-body.md

- name: Create branch and commit registry change
id: commit
env:
REPOSITORY: ${{ inputs.repository }}
REGISTRY_ARTIFACT: ${{ inputs.registry-artifact-name }}
REGISTRY_PATH: ${{ inputs.registry-file-path }}
run: |
BRANCH="qa/sync-ff-registry-$REPOSITORY"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$BRANCH"
mkdir -p "$(dirname "$REGISTRY_PATH")"
REGISTRY_SRC=$(find "./$REGISTRY_ARTIFACT" -name "$(basename "$REGISTRY_PATH")" -type f | head -1)
if [[ -z "$REGISTRY_SRC" ]]; then
echo "::error::No registry file found in artifact $REGISTRY_ARTIFACT"
exit 1
fi
cp "$REGISTRY_SRC" "$REGISTRY_PATH"
git add "$REGISTRY_PATH"
git status
if git diff --staged --quiet; then
echo "::warning::No changes to registry file. Nothing to commit."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
git commit -m "chore: sync feature flag registry from E2E"
git push --force-with-lease origin "$BRANCH"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force-with-lease fails when remote branch already exists

Medium Severity

git push --force-with-lease will fail on subsequent workflow runs when the remote branch already exists. Since actions/checkout@v6 only fetches the default branch, there is no local remote-tracking ref for the drift branch. Without a tracking ref, --force-with-lease treats the expected remote value as null and rejects the push if the branch exists remotely. The existing-PR handling at the "Create Pull Request" step is never reached because the workflow fails at the push step first.

Additional Locations (1)
Fix in Cursor Fix in Web

echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
fi

- name: Create Pull Request
id: create-pr
if: steps.commit.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ secrets.github-token }}
REPOSITORY: ${{ inputs.repository }}
BRANCH: ${{ steps.commit.outputs.branch }}
PR_LABEL: ${{ inputs.pr-label }}
run: |
EXISTING=$(gh pr list --head "$BRANCH" --state open --json url --jq '.[0].url' 2>/dev/null || true)
if [[ -n "$EXISTING" ]]; then
echo "pr-url=$EXISTING" >> "$GITHUB_OUTPUT"
exit 0
fi
LABEL_ARGS=()
if [[ -n "$PR_LABEL" ]]; then
LABEL_ARGS=(--label "$PR_LABEL")
fi
PR_URL=$(gh pr create \
--title "[QA] Sync Feature Flag Registry ($REPOSITORY)" \
--body-file pr-body.md \
--base main \
--head "$BRANCH" \
"${LABEL_ARGS[@]}")
echo "pr-url=$PR_URL" >> "$GITHUB_OUTPUT"
Loading