From 6e445ee8caedf5403125210c86c7e121e6f219d2 Mon Sep 17 00:00:00 2001 From: Anthony Raj Date: Mon, 27 Jul 2026 17:42:03 -0500 Subject: [PATCH 1/3] ci: add GitHub Action to trigger Bitrise crowdin_validator on PRs Bitrise's own GitHub webhook auto-registration is blocked for this app (needs a GitHub re-auth on the Bitrise account side), so bridge PR events to the Bitrise crowdin_validator workflow from GitHub Actions instead: trigger the build via the Bitrise API, wait for it, and fail this check if it fails. --- .github/workflows/crowdin-validator.yml | 108 ++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 .github/workflows/crowdin-validator.yml diff --git a/.github/workflows/crowdin-validator.yml b/.github/workflows/crowdin-validator.yml new file mode 100644 index 0000000..ad2a996 --- /dev/null +++ b/.github/workflows/crowdin-validator.yml @@ -0,0 +1,108 @@ +name: Crowdin validator (Bitrise) + +# Bridges GitHub PR events to the Bitrise `crowdin_validator` workflow. +# +# Bitrise's own GitHub webhook auto-registration is blocked for this app +# (`register_webhook` -> provider_reauth_required: "GitHub identity is not +# available for user") until someone reauthorizes Bitrise's GitHub connection +# in Bitrise account settings. Until then, this workflow is the "hook": it +# triggers the real validation logic on Bitrise (scripts/crowdin_validator.py) +# and fails this GitHub check if that build fails -- which is what branch +# protection actually needs to block a merge, regardless of whether Bitrise's +# own webhook is wired up. +# +# Requires one repo secret: BITRISE_API_TOKEN (a Bitrise personal access +# token, or workspace API token, with at least Developer access to the +# "loop-localization" app -- Bitrise -> Account Settings -> Security -> +# Personal access tokens). Not the Build trigger token; polling build status +# needs a real API token per Bitrise's docs. + +on: + pull_request: + branches: [main] + +concurrency: + group: crowdin-validator-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + crowdin_validator: + runs-on: ubuntu-latest + env: + BITRISE_APP_SLUG: acab23b7-43de-4110-8288-01fbc83fd63d + steps: + - name: Trigger Bitrise crowdin_validator build + id: trigger + env: + BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }} + run: | + set -euo pipefail + if [ -z "${BITRISE_API_TOKEN:-}" ]; then + echo "::error::Repo secret BITRISE_API_TOKEN is not set -- see comments in this workflow file." + exit 1 + fi + + payload=$(jq -n \ + --arg workflow_id "crowdin_validator" \ + --arg branch "${{ github.head_ref }}" \ + --arg branch_dest "${{ github.base_ref }}" \ + --arg commit_hash "${{ github.event.pull_request.head.sha }}" \ + --arg commit_message "${{ github.event.pull_request.title }}" \ + --arg pr_repo_url "${{ github.event.pull_request.head.repo.clone_url }}" \ + --argjson pr_id "${{ github.event.pull_request.number }}" \ + '{ + hook_info: {type: "bitrise"}, + build_params: { + workflow_id: $workflow_id, + branch: $branch, + branch_dest: $branch_dest, + commit_hash: $commit_hash, + commit_message: $commit_message, + pull_request_id: $pr_id, + pull_request_repository_url: $pr_repo_url, + pull_request_head_branch: $branch, + skip_git_status_report: true + } + }') + + response=$(curl -sS -w '\n%{http_code}' -X POST \ + "https://api.bitrise.io/v0.1/apps/${BITRISE_APP_SLUG}/builds" \ + -H "Authorization: ${BITRISE_API_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$payload") + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + echo "$body" + + if [ "$http_code" -ge 300 ]; then + echo "::error::Failed to trigger Bitrise build (HTTP $http_code)" + exit 1 + fi + + build_slug=$(echo "$body" | jq -er '.build_slug // .results[0].build_slug // empty') + build_url=$(echo "$body" | jq -er '.build_url // .results[0].build_url // empty') + echo "build_slug=$build_slug" >> "$GITHUB_OUTPUT" + echo "Triggered: $build_url" + echo "### Bitrise build: [$build_slug]($build_url)" >> "$GITHUB_STEP_SUMMARY" + + - name: Wait for Bitrise build result + env: + BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }} + BUILD_SLUG: ${{ steps.trigger.outputs.build_slug }} + run: | + set -euo pipefail + # status: 0 = in-progress, 1 = success, 2 = failed, 3 = aborted + for _ in $(seq 1 90); do + resp=$(curl -sS "https://api.bitrise.io/v0.1/apps/${BITRISE_APP_SLUG}/builds/${BUILD_SLUG}" \ + -H "Authorization: ${BITRISE_API_TOKEN}") + status=$(echo "$resp" | jq -r '.data.status') + status_text=$(echo "$resp" | jq -r '.data.status_text') + echo "Bitrise build status: $status ($status_text)" + case "$status" in + 1) echo "Bitrise crowdin_validator build succeeded."; exit 0 ;; + 2|3) echo "::error::Bitrise crowdin_validator build finished with status_text=$status_text"; exit 1 ;; + esac + sleep 10 + done + echo "::error::Timed out waiting for the Bitrise build to finish" + exit 1 From fce60c0dc601790e4aba38033c5ae6875487ee2b Mon Sep 17 00:00:00 2001 From: Anthony Raj Date: Mon, 27 Jul 2026 18:05:14 -0500 Subject: [PATCH 2/3] ci: use BITRISE_PAT secret name (matches what was actually added) --- .github/workflows/crowdin-validator.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/crowdin-validator.yml b/.github/workflows/crowdin-validator.yml index ad2a996..db43412 100644 --- a/.github/workflows/crowdin-validator.yml +++ b/.github/workflows/crowdin-validator.yml @@ -11,8 +11,8 @@ name: Crowdin validator (Bitrise) # protection actually needs to block a merge, regardless of whether Bitrise's # own webhook is wired up. # -# Requires one repo secret: BITRISE_API_TOKEN (a Bitrise personal access -# token, or workspace API token, with at least Developer access to the +# Requires one repo secret: BITRISE_PAT (a Bitrise personal access token, or +# workspace API token, with at least Developer access to the # "loop-localization" app -- Bitrise -> Account Settings -> Security -> # Personal access tokens). Not the Build trigger token; polling build status # needs a real API token per Bitrise's docs. @@ -34,11 +34,11 @@ jobs: - name: Trigger Bitrise crowdin_validator build id: trigger env: - BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }} + BITRISE_PAT: ${{ secrets.BITRISE_PAT }} run: | set -euo pipefail - if [ -z "${BITRISE_API_TOKEN:-}" ]; then - echo "::error::Repo secret BITRISE_API_TOKEN is not set -- see comments in this workflow file." + if [ -z "${BITRISE_PAT:-}" ]; then + echo "::error::Repo secret BITRISE_PAT is not set -- see comments in this workflow file." exit 1 fi @@ -67,7 +67,7 @@ jobs: response=$(curl -sS -w '\n%{http_code}' -X POST \ "https://api.bitrise.io/v0.1/apps/${BITRISE_APP_SLUG}/builds" \ - -H "Authorization: ${BITRISE_API_TOKEN}" \ + -H "Authorization: ${BITRISE_PAT}" \ -H "Content-Type: application/json" \ -d "$payload") http_code=$(echo "$response" | tail -n1) @@ -87,14 +87,14 @@ jobs: - name: Wait for Bitrise build result env: - BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }} + BITRISE_PAT: ${{ secrets.BITRISE_PAT }} BUILD_SLUG: ${{ steps.trigger.outputs.build_slug }} run: | set -euo pipefail # status: 0 = in-progress, 1 = success, 2 = failed, 3 = aborted for _ in $(seq 1 90); do resp=$(curl -sS "https://api.bitrise.io/v0.1/apps/${BITRISE_APP_SLUG}/builds/${BUILD_SLUG}" \ - -H "Authorization: ${BITRISE_API_TOKEN}") + -H "Authorization: ${BITRISE_PAT}") status=$(echo "$resp" | jq -r '.data.status') status_text=$(echo "$resp" | jq -r '.data.status_text') echo "Bitrise build status: $status ($status_text)" From 43989f287979dd239fddccd030e9f319c707741b Mon Sep 17 00:00:00 2001 From: Anthony Raj Date: Mon, 27 Jul 2026 18:07:17 -0500 Subject: [PATCH 3/3] ci: drop pull_request_* fields from the Bitrise trigger payload Bitrise tries to resolve pull_request_repository_url/pull_request_id against GitHub, which hits the same broken GitHub identity and makes the trigger call itself fail (HTTP 400: "GitHub identity is not available for user"). Trigger a plain branch build instead -- the validator script only does git fetch + diff, it never needs a PR merge ref. Also switches back to the BITRISE_API_TOKEN secret name. --- .github/workflows/crowdin-validator.yml | 31 +++++++++++++------------ 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/crowdin-validator.yml b/.github/workflows/crowdin-validator.yml index db43412..75c861b 100644 --- a/.github/workflows/crowdin-validator.yml +++ b/.github/workflows/crowdin-validator.yml @@ -11,11 +11,19 @@ name: Crowdin validator (Bitrise) # protection actually needs to block a merge, regardless of whether Bitrise's # own webhook is wired up. # -# Requires one repo secret: BITRISE_PAT (a Bitrise personal access token, or -# workspace API token, with at least Developer access to the +# Requires one repo secret: BITRISE_API_TOKEN (a Bitrise personal access +# token, or workspace API token, with at least Developer access to the # "loop-localization" app -- Bitrise -> Account Settings -> Security -> # Personal access tokens). Not the Build trigger token; polling build status # needs a real API token per Bitrise's docs. +# +# The trigger payload intentionally omits pull_request_* fields: Bitrise +# tries to resolve those against GitHub too, which hits the same broken +# identity and makes the *trigger* call itself fail with the same +# "GitHub identity is not available for user" error. Triggering a plain +# branch build (branch + commit_hash) sidesteps that entirely -- the +# validator script only needs `git fetch origin main` + a diff, not a PR +# merge ref. on: pull_request: @@ -34,40 +42,33 @@ jobs: - name: Trigger Bitrise crowdin_validator build id: trigger env: - BITRISE_PAT: ${{ secrets.BITRISE_PAT }} + BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }} run: | set -euo pipefail - if [ -z "${BITRISE_PAT:-}" ]; then - echo "::error::Repo secret BITRISE_PAT is not set -- see comments in this workflow file." + if [ -z "${BITRISE_API_TOKEN:-}" ]; then + echo "::error::Repo secret BITRISE_API_TOKEN is not set -- see comments in this workflow file." exit 1 fi payload=$(jq -n \ --arg workflow_id "crowdin_validator" \ --arg branch "${{ github.head_ref }}" \ - --arg branch_dest "${{ github.base_ref }}" \ --arg commit_hash "${{ github.event.pull_request.head.sha }}" \ --arg commit_message "${{ github.event.pull_request.title }}" \ - --arg pr_repo_url "${{ github.event.pull_request.head.repo.clone_url }}" \ - --argjson pr_id "${{ github.event.pull_request.number }}" \ '{ hook_info: {type: "bitrise"}, build_params: { workflow_id: $workflow_id, branch: $branch, - branch_dest: $branch_dest, commit_hash: $commit_hash, commit_message: $commit_message, - pull_request_id: $pr_id, - pull_request_repository_url: $pr_repo_url, - pull_request_head_branch: $branch, skip_git_status_report: true } }') response=$(curl -sS -w '\n%{http_code}' -X POST \ "https://api.bitrise.io/v0.1/apps/${BITRISE_APP_SLUG}/builds" \ - -H "Authorization: ${BITRISE_PAT}" \ + -H "Authorization: ${BITRISE_API_TOKEN}" \ -H "Content-Type: application/json" \ -d "$payload") http_code=$(echo "$response" | tail -n1) @@ -87,14 +88,14 @@ jobs: - name: Wait for Bitrise build result env: - BITRISE_PAT: ${{ secrets.BITRISE_PAT }} + BITRISE_API_TOKEN: ${{ secrets.BITRISE_API_TOKEN }} BUILD_SLUG: ${{ steps.trigger.outputs.build_slug }} run: | set -euo pipefail # status: 0 = in-progress, 1 = success, 2 = failed, 3 = aborted for _ in $(seq 1 90); do resp=$(curl -sS "https://api.bitrise.io/v0.1/apps/${BITRISE_APP_SLUG}/builds/${BUILD_SLUG}" \ - -H "Authorization: ${BITRISE_PAT}") + -H "Authorization: ${BITRISE_API_TOKEN}") status=$(echo "$resp" | jq -r '.data.status') status_text=$(echo "$resp" | jq -r '.data.status_text') echo "Bitrise build status: $status ($status_text)"