Validate a staging deployment after merge and promote to production. Runs browser QA or manual review against the staging URL, then creates a promotion PR (staging → production). Triggers on /flux:gate.
From fluxnpx claudepluginhub nairon-ai/flux --plugin fluxThis skill uses the workspace's default tool permissions.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
Executes pre-written implementation plans: critically reviews, follows bite-sized steps exactly, runs verifications, tracks progress with checkpoints, uses git worktrees, stops on blockers.
Guides idea refinement into designs: explores context, asks questions one-by-one, proposes approaches, presents sections for approval, writes/review specs before coding.
Validates that a staging deployment is healthy after a PR merge, then creates a promotion PR to production. This is the bridge between "code is merged to staging" and "code ships to production."
On entry, set the session phase:
PLUGIN_ROOT="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}}"
[ ! -d "$PLUGIN_ROOT/scripts" ] && PLUGIN_ROOT=$(ls -td ~/.claude/plugins/cache/nairon-flux/flux/*/ 2>/dev/null | head -1)
FLUXCTL="${PLUGIN_ROOT}/scripts/fluxctl"
$FLUXCTL session-phase set gate
On completion, reset:
$FLUXCTL session-phase set idle
environments.staging must be configured in .flux/config.json (set up via /flux:setup)PLUGIN_ROOT="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}}"
STAGING_BRANCH=$("${PLUGIN_ROOT}/scripts/fluxctl" config get environments.staging.branch --json 2>/dev/null | jq -r '.value // empty')
STAGING_URL=$("${PLUGIN_ROOT}/scripts/fluxctl" config get environments.staging.url --json 2>/dev/null | jq -r '.value // empty')
PROD_BRANCH=$("${PLUGIN_ROOT}/scripts/fluxctl" config get environments.production.branch --json 2>/dev/null | jq -r '.value // empty')
PLATFORM=$("${PLUGIN_ROOT}/scripts/fluxctl" config get environments.platform --json 2>/dev/null | jq -r '.value // empty')
INFRA_CLI=$("${PLUGIN_ROOT}/scripts/fluxctl" config get infrastructure.cli --json 2>/dev/null | jq -r '.value // empty')
If STAGING_BRANCH or STAGING_URL is empty, tell the user:
No staging environment configured. Run /flux:setup to set up environments.
Stop here.
First, check if the deployment is complete (for platforms with CLIs):
# Vercel
vercel ls --json 2>/dev/null | jq '[.deployments[] | select(.target=="production" and .state=="READY")] | .[0]'
# Railway
railway status 2>/dev/null
# Netlify
netlify api getSite --data '{}' 2>/dev/null | jq '.published_deploy.state'
# Cloudflare Pages — check latest branch deploy status
CF_PROJECT_NAME=$(grep -m1 'name' wrangler.toml 2>/dev/null | sed 's/.*=\s*"\?\([^"]*\)"\?/\1/' || jq -r '.name // empty' wrangler.json 2>/dev/null)
wrangler pages deployment list --project-name "$CF_PROJECT_NAME" 2>/dev/null | head -5
# Cloudflare Workers — check latest deployment to named environment
wrangler deployments list 2>/dev/null | head -5
# Workers staging is typically: wrangler deploy --env staging
# Generic fallback
curl -s -o /dev/null -w "%{http_code}" "$STAGING_URL"
If the staging URL returns a non-200 status or the platform reports the deployment isn't ready:
Tell the user:
Staging deployment is live at {STAGING_URL} ✓
Ask the user how they want to validate staging:
Staging is live. How do you want to validate?
1. Agent Browser — automated browser QA against {STAGING_URL}
2. Expect — AI-generated test plan from git diff, runs in real browser
3. Manual review — I'll show you what to check, you test and report back
Use AskUserQuestion for this.
Check if agent-browser is available:
command -v agent-browser >/dev/null 2>&1 && echo "available" || echo "not found"
If not available, tell the user and fall back to expect-cli (Step 4c) or manual review (Step 4b).
If available, look for the Browser QA checklist from the most recently completed epic:
# Find the most recent epic's QA task
LATEST_EPIC=$("${PLUGIN_ROOT}/scripts/fluxctl" list-epics --json 2>/dev/null | jq -r '.[0].id // empty')
If a QA checklist exists, run each criterion against the staging URL using agent-browser:
agent-browser open "$STAGING_URL"
agent-browser wait --load networkidle
# Run through each acceptance criterion from the QA checklist
# Take screenshots as evidence
agent-browser screenshot "/tmp/staging-qa-$(date +%s).png"
After testing, report results:
Present the QA checklist to the user (or a general checklist if none exists):
Please review staging at: {STAGING_URL}
Checklist:
{QA checklist items from epic, or generic items:}
- [ ] App loads without errors
- [ ] Core user flows work (login, main actions, etc.)
- [ ] No console errors in browser dev tools
- [ ] New feature/fix from the merged PR works as expected
- [ ] No visual regressions on key pages
Report back with what you found — "all good" or describe any issues.
Wait for the user's response. If issues reported, same options as 4a failures.
expect-cli (https://www.expect.dev) analyzes git changes, AI-generates a test strategy, and runs it in a real browser.
# Run expect-cli — it auto-detects changes and generates browser tests
npx expect-cli
expect-cli will:
After testing, report results:
Check if the project has e2e tests:
# Check package.json for e2e/test:e2e scripts
E2E_SCRIPT=$(jq -r '.scripts["test:e2e"] // .scripts["e2e"] // empty' package.json 2>/dev/null)
If available, run them against the staging URL:
BASE_URL="$STAGING_URL" npm run test:e2e 2>&1
# Or if using Playwright:
PLAYWRIGHT_BASE_URL="$STAGING_URL" npx playwright test 2>&1
Report results. Failures don't block promotion — the user decides.
If validation passes (or user chose to proceed):
# Ensure we have the latest staging and production branches
git fetch origin "$STAGING_BRANCH" "$PROD_BRANCH"
# Check if there are commits to promote
COMMITS=$(git log "origin/$PROD_BRANCH..origin/$STAGING_BRANCH" --oneline 2>/dev/null)
if [ -z "$COMMITS" ]; then
echo "No new commits to promote. Staging and production are in sync."
# Stop here
fi
Create the promotion PR:
gh pr create \
--base "$PROD_BRANCH" \
--head "$STAGING_BRANCH" \
--title "promote: staging → production" \
--body "$(cat <<'EOF'
## Staging Validation
**Staging URL**: {STAGING_URL}
**Validation method**: {Agent Browser / Manual review}
**Result**: All checks passed ✓
### Changes included
{list of commits being promoted}
### Evidence
{screenshots or test results if available}
---
Merge this PR to deploy to production.
EOF
)"
Tell the user:
Promotion PR created: {PR_URL}
Merge when ready to deploy to production. Flux will not auto-merge to production.
ALWAYS run at the very end of command execution:
PLUGIN_ROOT="${DROID_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}}"
CURRENT_VERSION=$(jq -r '.version' "${PLUGIN_ROOT}/.claude-plugin/plugin.json" 2>/dev/null)
MARKETPLACE_VERSION=$(jq -r '.plugins[0].version' "${PLUGIN_ROOT}/.claude-plugin/marketplace.json" 2>/dev/null)
if [ "$CURRENT_VERSION" != "$MARKETPLACE_VERSION" ] && [ -n "$MARKETPLACE_VERSION" ]; then
echo "Update available: v${CURRENT_VERSION} → v${MARKETPLACE_VERSION}. Run /flux:upgrade"
fi