Skip to content

merge queue: queuing main (d5ee340) and #556 together #1135

merge queue: queuing main (d5ee340) and #556 together

merge queue: queuing main (d5ee340) and #556 together #1135

Workflow file for this run

name: PR Status Check
on:
pull_request:
branches: [main]
merge_group:
types: [checks_requested]
# This workflow acts as the single required status check for all PRs.
# It uses a semaphore-based polling approach with improved reliability.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_sha }}
cancel-in-progress: true
jobs:
detect-changes:
name: Detect Changes
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
docs-only: ${{ steps.changes.outputs.docs-only }}
clients-only: ${{ steps.changes.outputs.clients-only }}
code-changes: ${{ steps.changes.outputs.code-changes }}
workflow-only: ${{ steps.changes.outputs.workflow-only }}
has-docs: ${{ steps.changes.outputs.has-docs }}
has-clients: ${{ steps.changes.outputs.has-clients }}
has-workflow: ${{ steps.changes.outputs.has-workflow }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Detect changed files
id: changes
run: |
# Get the base ref for comparison
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_REF="${{ github.event.pull_request.base.sha }}"
else
BASE_REF="origin/main"
fi
# Get list of changed files
CHANGED_FILES=$(git diff --name-only "$BASE_REF" HEAD)
# Define patterns for different change types
# Workflow files take precedence - they may not trigger CI runs due to GitHub security
WORKFLOW_PATTERN='(^\.github/workflows/|^\.mergify\.yml$|^\.github/.*\.md$|^Taskfile\.yml$)'
CLIENTS_PATTERN='(^clients/|^\.github/workflows/client-sdk-ci\.yml)'
DOCS_PATTERN='(^docs-cms/|^docusaurus/(docs|blog|i18n|sidebars|src|static|docusaurus\.config)|^docusaurus/\.[^/]+$|^docusaurus/[^/]+\.(json|ts)$|^tooling/(build_docs|validate_docs)\.py|^\.github/workflows/docs\.yml|^\.claude/|^[A-Z_-]+\.md$|^docs-summary\.md$)'
# Categorize changes (order matters - workflow first, then clients, then docs)
WORKFLOW_FILES=$(echo "$CHANGED_FILES" | grep -E "${WORKFLOW_PATTERN}" || true)
CLIENT_FILES=$(echo "$CHANGED_FILES" | grep -vE "${WORKFLOW_PATTERN}" | grep -E "${CLIENTS_PATTERN}" || true)
DOCS_FILES=$(echo "$CHANGED_FILES" | grep -vE "${WORKFLOW_PATTERN}" | grep -vE "${CLIENTS_PATTERN}" | grep -E "${DOCS_PATTERN}" || true)
OTHER_FILES=$(echo "$CHANGED_FILES" | grep -vE "${WORKFLOW_PATTERN}" | grep -vE "${CLIENTS_PATTERN}" | grep -vE "${DOCS_PATTERN}" || true)
# Set individual flags
HAS_DOCS="false"
HAS_CLIENTS="false"
HAS_WORKFLOW="false"
HAS_CODE="false"
[ -n "$DOCS_FILES" ] && HAS_DOCS="true"
[ -n "$CLIENT_FILES" ] && HAS_CLIENTS="true"
[ -n "$WORKFLOW_FILES" ] && HAS_WORKFLOW="true"
[ -n "$OTHER_FILES" ] && HAS_CODE="true"
# Output individual flags
{
echo "has-docs=${HAS_DOCS}"
echo "has-clients=${HAS_CLIENTS}"
echo "has-workflow=${HAS_WORKFLOW}"
} >> "$GITHUB_OUTPUT"
# Determine exclusive categories
if [[ "$HAS_WORKFLOW" == "true" && "$HAS_DOCS" == "false" && "$HAS_CLIENTS" == "false" && "$HAS_CODE" == "false" ]]; then
{
echo "docs-only=false"
echo "clients-only=false"
echo "code-changes=false"
echo "workflow-only=true"
} >> "$GITHUB_OUTPUT"
echo "⚙️ Detected: Workflow/CI configuration-only changes"
elif [[ "$HAS_DOCS" == "true" && "$HAS_CLIENTS" == "false" && "$HAS_CODE" == "false" && "$HAS_WORKFLOW" == "false" ]]; then
{
echo "docs-only=true"
echo "clients-only=false"
echo "code-changes=false"
echo "workflow-only=false"
} >> "$GITHUB_OUTPUT"
echo "📄 Detected: Documentation-only changes"
elif [[ "$HAS_CLIENTS" == "true" && "$HAS_DOCS" == "false" && "$HAS_CODE" == "false" && "$HAS_WORKFLOW" == "false" ]]; then
{
echo "docs-only=false"
echo "clients-only=true"
echo "code-changes=false"
echo "workflow-only=false"
} >> "$GITHUB_OUTPUT"
echo "📱 Detected: Client SDK-only changes"
elif [[ "$HAS_CODE" == "true" ]]; then
{
echo "docs-only=false"
echo "clients-only=false"
echo "code-changes=true"
echo "workflow-only=false"
} >> "$GITHUB_OUTPUT"
echo "💻 Detected: Code changes (full CI required)"
else
{
echo "docs-only=false"
echo "clients-only=false"
echo "code-changes=true"
echo "workflow-only=false"
} >> "$GITHUB_OUTPUT"
echo "🔀 Detected: Mixed changes (full CI required)"
fi
# Semaphore job that waits for workflow completion signals
pr-status:
name: PR Status Check
runs-on: ubuntu-latest
timeout-minutes: 90
needs: detect-changes
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Wait for required workflow completions
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DOCS_ONLY="${{ needs.detect-changes.outputs.docs-only }}"
CLIENTS_ONLY="${{ needs.detect-changes.outputs.clients-only }}"
CODE_CHANGES="${{ needs.detect-changes.outputs.code-changes }}"
WORKFLOW_ONLY="${{ needs.detect-changes.outputs.workflow-only }}"
HAS_DOCS="${{ needs.detect-changes.outputs.has-docs }}"
HAS_CLIENTS="${{ needs.detect-changes.outputs.has-clients }}"
HAS_WORKFLOW="${{ needs.detect-changes.outputs.has-workflow }}"
SHA="${{ github.event.pull_request.head.sha || github.sha }}"
MAX_WAIT=5400 # 90 minutes in seconds
CHECK_INTERVAL=15 # Check every 15 seconds (faster than before)
ELAPSED=0
STARTUP_GRACE=180 # 3 minute grace period for workflows to start
{
echo "## PR Status Check (Semaphore)"
echo ""
echo "**SHA**: \`$SHA\`"
echo "**PR**: #${{ github.event.pull_request.number }}"
echo ""
echo "### Change Detection"
echo "- Documentation-only: ${DOCS_ONLY}"
echo "- Client SDK-only: ${CLIENTS_ONLY}"
echo "- Code changes: ${CODE_CHANGES}"
echo "- Workflow-only: ${WORKFLOW_ONLY}"
echo "- Has docs: ${HAS_DOCS}"
echo "- Has clients: ${HAS_CLIENTS}"
echo "- Has workflow: ${HAS_WORKFLOW}"
echo ""
} >> "$GITHUB_STEP_SUMMARY"
# Determine which workflow names we need to wait for
REQUIRED_WORKFLOWS=()
if [[ "$WORKFLOW_ONLY" == "true" ]]; then
# Workflow-only changes don't require waiting for any workflows
# GitHub may not run CI workflows when workflow files themselves are modified (security feature)
{
echo "### ✅ Status: SUCCESS (No workflows required)"
echo ""
echo "This PR only modifies CI/workflow configuration files."
echo "No other workflows need to complete for this change type."
echo ""
echo "**Note**: Workflow changes are validated through:"
echo "1. YAML syntax validation by GitHub"
echo "2. Post-merge testing when the workflows actually run"
echo "3. Manual review of the workflow changes"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
elif [[ "$DOCS_ONLY" == "true" ]]; then
REQUIRED_WORKFLOWS+=("Docs PR Validation")
elif [[ "$CLIENTS_ONLY" == "true" ]]; then
REQUIRED_WORKFLOWS+=("Client SDK CI")
elif [[ "$CODE_CHANGES" == "true" ]]; then
REQUIRED_WORKFLOWS+=("CI")
[[ "$HAS_DOCS" == "true" ]] && REQUIRED_WORKFLOWS+=("Docs PR Validation")
[[ "$HAS_CLIENTS" == "true" ]] && REQUIRED_WORKFLOWS+=("Client SDK CI")
fi
{
echo "### Required Workflows"
for WF in "${REQUIRED_WORKFLOWS[@]}"; do
echo "- \`$WF\`"
done
echo ""
} >> "$GITHUB_STEP_SUMMARY"
echo "Waiting for: ${REQUIRED_WORKFLOWS[*]}"
# Function to get workflow run status
# Query recent runs first (fast path), then fall back to deeper search if needed.
# This avoids iterating through all workflow runs which can take 80+ seconds in large repos.
get_workflow_status() {
local workflow_name="$1"
local result=""
# Fast path: Check first 100 runs (covers 99% of cases for recent PRs)
result=$(gh api "repos/${{ github.repository }}/actions/runs?per_page=100" \
--jq ".workflow_runs[] | select(.name == \"$workflow_name\" and .head_sha == \"$SHA\") | {status: .status, conclusion: .conclusion, created_at: .created_at}" \
2>/dev/null | jq -s 'sort_by(.created_at) | reverse | .[0] | del(.created_at)')
# If found, return it
if [ -n "$result" ] && [ "$result" != "null" ]; then
echo "$result"
return
fi
# Slow path: Check first 500 runs (handles edge cases)
result=$(gh api "repos/${{ github.repository }}/actions/runs?per_page=100&page=1" \
--jq ".workflow_runs[] | select(.name == \"$workflow_name\" and .head_sha == \"$SHA\") | {status: .status, conclusion: .conclusion}" \
2>/dev/null)
if [ -z "$result" ]; then
result=$(gh api "repos/${{ github.repository }}/actions/runs?per_page=100&page=2" \
--jq ".workflow_runs[] | select(.name == \"$workflow_name\" and .head_sha == \"$SHA\") | {status: .status, conclusion: .conclusion}" \
2>/dev/null)
fi
if [ -z "$result" ]; then
result=$(gh api "repos/${{ github.repository }}/actions/runs?per_page=100&page=3" \
--jq ".workflow_runs[] | select(.name == \"$workflow_name\" and .head_sha == \"$SHA\") | {status: .status, conclusion: .conclusion}" \
2>/dev/null)
fi
if [ -z "$result" ]; then
result=$(gh api "repos/${{ github.repository }}/actions/runs?per_page=100&page=4" \
--jq ".workflow_runs[] | select(.name == \"$workflow_name\" and .head_sha == \"$SHA\") | {status: .status, conclusion: .conclusion}" \
2>/dev/null)
fi
if [ -z "$result" ]; then
result=$(gh api "repos/${{ github.repository }}/actions/runs?per_page=100&page=5" \
--jq ".workflow_runs[] | select(.name == \"$workflow_name\" and .head_sha == \"$SHA\") | {status: .status, conclusion: .conclusion}" \
2>/dev/null)
fi
echo "$result"
}
# Wait for all required workflows to complete
LAST_STATUS=""
while [ $ELAPSED -lt $MAX_WAIT ]; do
ALL_COMPLETE=true
ALL_SUCCESS=true
PENDING_WORKFLOWS=()
FAILED_WORKFLOWS=()
SUCCESS_WORKFLOWS=()
for WORKFLOW in "${REQUIRED_WORKFLOWS[@]}"; do
WORKFLOW_DATA=$(get_workflow_status "$WORKFLOW")
if [ -n "$WORKFLOW_DATA" ]; then
STATUS=$(echo "$WORKFLOW_DATA" | jq -r '.status')
CONCLUSION=$(echo "$WORKFLOW_DATA" | jq -r '.conclusion')
if [[ "$STATUS" == "completed" ]]; then
if [[ "$CONCLUSION" == "success" ]]; then
SUCCESS_WORKFLOWS+=("$WORKFLOW")
else
FAILED_WORKFLOWS+=("$WORKFLOW ($CONCLUSION)")
ALL_SUCCESS=false
fi
else
ALL_COMPLETE=false
PENDING_WORKFLOWS+=("$WORKFLOW ($STATUS)")
fi
else
# Workflow hasn't started yet
ALL_COMPLETE=false
if [ $ELAPSED -lt $STARTUP_GRACE ]; then
PENDING_WORKFLOWS+=("$WORKFLOW (starting...)")
else
# After grace period, this is an error
PENDING_WORKFLOWS+=("$WORKFLOW (NOT STARTED - ERROR)")
fi
fi
done
# Generate status update
CURRENT_STATUS="Success: ${#SUCCESS_WORKFLOWS[@]} | Pending: ${#PENDING_WORKFLOWS[@]} | Failed: ${#FAILED_WORKFLOWS[@]}"
# Only print if status changed
if [ "$CURRENT_STATUS" != "$LAST_STATUS" ]; then
echo "[$ELAPSED s] $CURRENT_STATUS"
LAST_STATUS="$CURRENT_STATUS"
fi
# Check if we're done
if [[ "$ALL_COMPLETE" == "true" ]]; then
if [[ "$ALL_SUCCESS" == "true" ]]; then
{
echo "### ✅ Status: SUCCESS"
echo ""
echo "All required workflows completed successfully:"
for WF in "${SUCCESS_WORKFLOWS[@]}"; do
echo "- ✅ \`$WF\`"
done
echo ""
echo "**Total wait time**: ${ELAPSED}s"
} >> "$GITHUB_STEP_SUMMARY"
exit 0
else
{
echo "### ❌ Status: FAILURE"
echo ""
echo "Failed workflows:"
for WF in "${FAILED_WORKFLOWS[@]}"; do
echo "- ❌ \`$WF\`"
done
if [ ${#SUCCESS_WORKFLOWS[@]} -gt 0 ]; then
echo ""
echo "Successful workflows:"
for WF in "${SUCCESS_WORKFLOWS[@]}"; do
echo "- ✅ \`$WF\`"
done
fi
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
fi
# Still waiting - check for stalled workflows
if [ $ELAPSED -gt $STARTUP_GRACE ]; then
for WF in "${PENDING_WORKFLOWS[@]}"; do
if [[ "$WF" == *"NOT STARTED"* ]]; then
{
echo "### ❌ Status: ERROR"
echo ""
echo "Workflow \`${WF%% (*)}\` failed to start within ${STARTUP_GRACE}s grace period."
echo ""
echo "This usually indicates:"
echo "1. Path filters prevented the workflow from triggering"
echo "2. Workflow file has syntax errors"
echo "3. GitHub Actions is experiencing issues"
echo ""
echo "Please check the workflow file and GitHub Actions status."
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi
done
fi
sleep $CHECK_INTERVAL
ELAPSED=$((ELAPSED + CHECK_INTERVAL))
done
# Timeout
{
echo "### ❌ Status: TIMEOUT"
echo ""
echo "Timed out after ${MAX_WAIT}s waiting for workflows to complete."
echo ""
if [ ${#PENDING_WORKFLOWS[@]} -gt 0 ]; then
echo "Pending workflows:"
for WF in "${PENDING_WORKFLOWS[@]}"; do
echo "- ⏳ \`$WF\`"
done
fi
if [ ${#FAILED_WORKFLOWS[@]} -gt 0 ]; then
echo ""
echo "Failed workflows:"
for WF in "${FAILED_WORKFLOWS[@]}"; do
echo "- ❌ \`$WF\`"
done
fi
if [ ${#SUCCESS_WORKFLOWS[@]} -gt 0 ]; then
echo ""
echo "Successful workflows:"
for WF in "${SUCCESS_WORKFLOWS[@]}"; do
echo "- ✅ \`$WF\`"
done
fi
} >> "$GITHUB_STEP_SUMMARY"
exit 1