Skip to content

ci: consolidate workflows into ci.yml #2940

ci: consolidate workflows into ci.yml

ci: consolidate workflows into ci.yml #2940

name: Claude Translation Review
on:
workflow_dispatch:
inputs:
pr_number:
description: "PR number to review"
required: true
type: number
language:
description: "Language code(s) to review (comma-separated, e.g. 'hi' or 'hi,bn')"
required: false
type: string
full:
description: "Re-review the entire PR (ignore prior-review SHA, force full diff)"
required: false
default: false
type: boolean
model:
description: "Claude model for analysis"
required: false
default: "opus"
type: choice
options:
- opus
- sonnet
- haiku
fix:
description: "Automatically fix critical translation issues"
required: false
default: false
type: boolean
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
pull_request:
types: [opened]
jobs:
review-translations:
# Runs when:
# 1. Comment contains @claude /review-translations (from authorized user), OR
# 2. PR is opened with title starting with "i18n:" (automatic), OR
# 3. Manually dispatched from Actions tab with a PR number
if: |
github.event_name == 'workflow_dispatch' ||
(
github.event_name == 'issue_comment' &&
contains(github.event.comment.body, '@claude') &&
contains(github.event.comment.body, '/review-translations') &&
contains(fromJSON('["minimalsm","pettinarip","wackerow","nloureiro","konopkja","mnelsonBT","lukassim"]'), github.event.comment.user.login) &&
github.event.issue.pull_request
) ||
(
github.event_name == 'pull_request_review_comment' &&
contains(github.event.comment.body, '@claude') &&
contains(github.event.comment.body, '/review-translations') &&
contains(fromJSON('["minimalsm","pettinarip","wackerow","nloureiro","konopkja","mnelsonBT","lukassim"]'), github.event.comment.user.login)
) ||
(
github.event_name == 'pull_request' &&
startsWith(github.event.pull_request.title, 'i18n:') &&
startsWith(github.event.pull_request.head.ref, 'intl/pending-') &&
github.event.pull_request.head.repo.full_name == github.repository &&
contains(fromJSON('["minimalsm","pettinarip","wackerow","nloureiro","konopkja","mnelsonBT","lukassim"]'), github.event.pull_request.user.login)
)
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get PR number
id: pr
env:
# All values moved to env block to prevent shell injection
EVENT_NAME: ${{ github.event_name }}
INPUT_PR_NUMBER: ${{ github.event.inputs.pr_number }}
PR_NUMBER: ${{ github.event.pull_request.number }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
PR_NUM="$INPUT_PR_NUMBER"
elif [[ "$EVENT_NAME" == "pull_request" || "$EVENT_NAME" == "pull_request_review_comment" ]]; then
PR_NUM="$PR_NUMBER"
else
PR_NUM="$ISSUE_NUMBER"
fi
# Validate PR number is numeric to prevent injection in downstream usage
if [[ ! "$PR_NUM" =~ ^[0-9]+$ ]]; then
echo "Error: PR number must be numeric, got: $PR_NUM"
exit 1
fi
echo "number=$PR_NUM" >> $GITHUB_OUTPUT
- name: Extract flags from comment
id: parse
env:
EVENT_NAME: ${{ github.event_name }}
INPUT_LANGUAGE: ${{ github.event.inputs.language }}
INPUT_FULL: ${{ github.event.inputs.full }}
INPUT_MODEL: ${{ github.event.inputs.model }}
INPUT_FIX: ${{ github.event.inputs.fix }}
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
# For automatic triggers (pull_request), use defaults (incremental scope)
if [[ "$EVENT_NAME" == "pull_request" ]]; then
echo "language_flag=" >> $GITHUB_OUTPUT
echo "full_flag=" >> $GITHUB_OUTPUT
echo "model=opus" >> $GITHUB_OUTPUT
echo "fix_flag=" >> $GITHUB_OUTPUT
exit 0
fi
# For manual dispatch, read directly from inputs
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
if [[ -n "$INPUT_LANGUAGE" && "$INPUT_LANGUAGE" =~ ^[a-zA-Z,-]+$ ]]; then
echo "language_flag=--language=${INPUT_LANGUAGE}" >> $GITHUB_OUTPUT
else
echo "language_flag=" >> $GITHUB_OUTPUT
fi
# INPUT_MODEL is a choice type, safe values enforced by GitHub
echo "model=${INPUT_MODEL}" >> $GITHUB_OUTPUT
if [[ "$INPUT_FULL" == "true" ]]; then
echo "full_flag=--full" >> $GITHUB_OUTPUT
else
echo "full_flag=" >> $GITHUB_OUTPUT
fi
if [[ "$INPUT_FIX" == "true" ]]; then
echo "fix_flag=--fix" >> $GITHUB_OUTPUT
else
echo "fix_flag=" >> $GITHUB_OUTPUT
fi
exit 0
fi
# COMMENT_BODY is passed via env to prevent shell injection.
# Extract --language flag if present
if [[ "$COMMENT_BODY" =~ --language=([a-zA-Z,-]+) ]]; then
echo "language_flag=--language=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
else
echo "language_flag=" >> $GITHUB_OUTPUT
fi
# Extract --full flag if present (override incremental, re-review entire PR)
if [[ "$COMMENT_BODY" =~ --full([[:space:]]|$) ]]; then
echo "full_flag=--full" >> $GITHUB_OUTPUT
else
echo "full_flag=" >> $GITHUB_OUTPUT
fi
# Extract --model flag if present (default to opus per skill spec)
if [[ "$COMMENT_BODY" =~ --model=(opus|sonnet|haiku) ]]; then
echo "model=${BASH_REMATCH[1]}" >> $GITHUB_OUTPUT
else
echo "model=opus" >> $GITHUB_OUTPUT
fi
# Extract --fix flag if present
if [[ "$COMMENT_BODY" =~ --fix ]]; then
echo "fix_flag=--fix" >> $GITHUB_OUTPUT
else
echo "fix_flag=" >> $GITHUB_OUTPUT
fi
- name: Post acknowledgment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ steps.pr.outputs.number }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
gh pr comment "$PR_NUMBER" --body "$(cat <<EOF
:globe_with_meridians: **Translation review started.** [View progress]($RUN_URL)
EOF
)"
- name: Run Claude Translation Review
uses: anthropics/claude-code-action@v1
timeout-minutes: 120
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
claude_args: |
--model ${{ steps.parse.outputs.model }}
--allowedTools "Task,Glob,Grep,LS,Read,Edit,WebFetch,Bash(git status:*),Bash(git diff:*),Bash(git log:*),Bash(git fetch:*),Bash(git worktree:*),Bash(git add:*),Bash(git commit:*),Bash(gh api:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh pr comment:*),Bash(gh pr review:*)"
prompt: |
Execute the /review-translations command for PR #${{ steps.pr.outputs.number }}.
Arguments: --pr=${{ steps.pr.outputs.number }} ${{ steps.parse.outputs.language_flag }} ${{ steps.parse.outputs.full_flag }} ${{ steps.parse.outputs.fix_flag }}
Follow the instructions in .claude/commands/review-translations.md for Phase 0 (scope detection -- read prior review SHA from gh api .../reviews unless --full was passed), Phase 1 (parallel agents), and Phase 2 (collecting results).
IMPORTANT workflow modifications for GitHub Actions context:
1. Use parallel Task agents (ONE agent per language) as specified in the skill.
2. SUBMIT THE REVIEW AS A PROPER PR REVIEW, NOT AN ISSUE COMMENT.
This is mandatory: the next /review-translations invocation reads each PR Review's GitHub-attached `commit_id` to determine incremental scope. An issue comment (`gh pr comment`) does not carry a `commit_id` and would break the incremental flow.
Write the body to a temp file (to avoid heredoc issues), then submit via `gh pr review`, which auto-attaches the current PR HEAD SHA:
gh pr review ${{ steps.pr.outputs.number }} --comment --body-file /tmp/pr-review-body.md
Default to `--comment`. Use `--approve` instead only when the review turned up ZERO critical issues (none found, or all auto-fixed in this same run). Never use `--request-changes`.
3. Body length: aim for a SINGLE review body. If it exceeds GitHub's body limit (~65k chars), keep the per-language summary table + scoring inside the Review body, and post ONLY the verbose per-language detail dumps as follow-up issue comments via `gh pr comment` (NOT additional Reviews -- supplemental detail does not need its own SHA marker).
4. The review body MUST include a `**Fixes:**` line near the top, with one of:
- `Critical fixes applied: {N}` -- if --fix was passed AND fixes were committed (NOTE: this GitHub Actions context CANNOT auto-fix on the PR branch because we are not running in the PR branch's worktree; for now this case will not occur from CI)
- `No fixes applied (review-only)` -- always the value when run from this workflow
- `No critical issues found` -- when there are no critical issues at all
This standardizes the format with the local /review-translations command.
5. Auto-fix behavior - check if --fix flag is present in Arguments above:
- If --fix flag IS present: this CI workflow is not currently set up to commit fixes back to the PR branch. Note this in the review body and recommend running locally: `/review-translations --pr=${{ steps.pr.outputs.number }} --fix`
- If --fix flag is NOT present: Do NOT apply fixes. At the end of your review body, if there are critical issues, include:
---
**To apply fixes**, run locally:
```
/review-translations --pr=${{ steps.pr.outputs.number }} --fix
```
6. Do NOT prompt for user input with AskUserQuestion - this is fully automated.