mirror of
https://github.com/react/react-native.git
synced 2026-09-28 05:13:06 +08:00
Summary: Artifact uploads can fail on transient network errors such as the `ETIMEDOUT` in https://github.com/react/react-native/actions/runs/35598907154/job/106330670539. This change: - adds a local composite action backed by `actions/upload-artifact` v7.0.1 - retries failed uploads twice, waiting 10 seconds before attempt 2 and 20 seconds before attempt 3 - propagates the third failure to the caller - preserves all v7 inputs and outputs - migrates all 35 artifact upload call sites to the wrapper ## Changelog: [INTERNAL] [FIXED] - Retry artifact uploads to reduce transient CI failures. Pull Request resolved: https://github.com/react/react-native/pull/58620 Test Plan: - `node_modules/.bin/prettier --check $(git diff --name-only HEAD^ -- '*.yml' '*.yaml')` — passed - `npx --yes action-validator/cli@0.6.0 .github/actions/upload-artifact/action.yml` — passed - `git diff HEAD^ --check` — passed - Verified no `actions/upload-artifact@v6` references remain under `.github` Reviewed By: andrewdacenko Differential Revision: D121002745 Pulled By: cortinico fbshipit-source-id: e63e30f3e86b0a2cb6c94aa549cdfe5e6dfbdc2b
180 lines
9.2 KiB
YAML
180 lines
9.2 KiB
YAML
# @ref LLP 0009#workflow-security-posture — issue_comment is not fork-restricted; runs with full secrets regardless of PR origin
|
|
name: AI code review (command)
|
|
|
|
# On-demand, ONE-SHOT reviewer triggered by a PR comment (maintainers only):
|
|
# /review run once now; the router picks the agents
|
|
# /review all run once with every agent
|
|
# /review correctness security run once with just those agents
|
|
# This never changes configuration. CONTINUOUS review is configured in
|
|
# expo-code-review.yml (the `pull_request` workflow) via the `review.trigger`
|
|
# policy in .expo-code-review/config.jsonc and the `ai-review:skip` label.
|
|
|
|
on:
|
|
issue_comment:
|
|
types: [created]
|
|
|
|
# Comment-only: read the repo, write PR comments (issue comments API).
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
issues: write
|
|
|
|
env:
|
|
# Published reviewer run via npx (override with repo variable ECR_VERSION; pin to
|
|
# a specific version to freeze it). Used for the guard AND the review so the engine
|
|
# that clears a config is the same engine that then reads it.
|
|
ECR_VERSION: ${{ vars.ECR_VERSION || '0.14.0' }}
|
|
|
|
concurrency:
|
|
group: ai-code-review-cmd-${{ github.event.issue.number }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
command:
|
|
# Only PR comments starting with /review, from a maintainer.
|
|
# @ref LLP 0009#workflow-security-posture [implements] — gate controls who triggers, not what code runs
|
|
if: >-
|
|
github.event.issue.pull_request != null &&
|
|
startsWith(github.event.comment.body, '/review') &&
|
|
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
|
|
runs-on: ubuntu-latest
|
|
# Bound the run so a slow/stalled review fails fast rather than hanging. Keep it
|
|
# above the passes budget (budget.totalPassesMinutes, 55m) + coordinator (10m) +
|
|
# verification + setup, like the auto-review workflow's cap.
|
|
timeout-minutes: 90
|
|
# A reviewer failure must never fail the PR's checks.
|
|
continue-on-error: true
|
|
steps:
|
|
# @ref LLP 0009#workflow-security-posture [implements] — comment body only via env:; agent ids sanitized before reaching argv
|
|
- name: Parse command
|
|
id: cmd
|
|
env:
|
|
# Via env (never inline ${{ }}) so an untrusted comment can't inject shell.
|
|
COMMENT: ${{ github.event.comment.body }}
|
|
run: |
|
|
line=$(printf '%s' "$COMMENT" | head -n1 | tr -d '\r')
|
|
verb=$(printf '%s' "$line" | awk '{print $1}')
|
|
rest=$(printf '%s' "$line" | cut -s -d' ' -f2-)
|
|
# Only /review (one-shot). Continuous review is policy/label-driven, not a
|
|
# comment; /review no longer changes any configuration.
|
|
if [ "$verb" != "/review" ]; then
|
|
echo "run=false" >> "$GITHUB_OUTPUT"; exit 0
|
|
fi
|
|
# Bare "/review" -> router picks; "all" -> every agent; names -> subset.
|
|
# Sanitize agent ids to [a-zA-Z0-9,_-] to keep the value shell-safe.
|
|
agents=""
|
|
route=false
|
|
if [ -z "$rest" ]; then
|
|
route=true
|
|
elif [ "$rest" != "all" ]; then
|
|
agents=$(printf '%s' "$rest" | tr ' ' ',' | tr -cd 'a-zA-Z0-9,_-')
|
|
fi
|
|
{
|
|
echo "run=true"
|
|
echo "agents=$agents"
|
|
echo "route=$route"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Acknowledge
|
|
if: steps.cmd.outputs.run == 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: gh api -X POST "repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" -f content=eyes
|
|
|
|
# SECURITY: `issue_comment` is NOT fork-restricted by GitHub — it always
|
|
# runs in the base-repo context with full secrets and a write-scoped token,
|
|
# regardless of whether the commented-on PR is from a fork. We check out ONLY
|
|
# the trusted base ref (the default branch) for the `.expo-code-review/`
|
|
# config, and never `gh pr checkout` the PR head. The reviewer engine itself
|
|
# is the PUBLISHED @expo/code-review-cli (fetched by npx), not built from any
|
|
# checkout, so attacker-controlled PR code never runs here. The diff + PR
|
|
# metadata come from the API (`gh pr diff`/`gh pr view`); `ecr ci` loads
|
|
# configuration from the PR's immutable base commit and reads source from a
|
|
# head worktree scrubbed of ambient runtime config (opencode.json, plugins,
|
|
# AGENTS.md, .env, …) — the same trust model as the pull_request workflow.
|
|
- name: Checkout (base ref only — never the PR head)
|
|
if: steps.cmd.outputs.run == 'true'
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 1
|
|
# The CLI's own git fetches authenticate through `gh` from GH_TOKEN, so
|
|
# the token never lands in .git/config.
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node
|
|
if: steps.cmd.outputs.run == 'true'
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: 24
|
|
# The reviewer runs via npx and never installs with a package manager, so
|
|
# disable setup-node's auto package-manager cache (its post step would try
|
|
# to save an empty cache and error).
|
|
package-manager-cache: false
|
|
|
|
# SECURITY: the base-ref checkout above includes every .expo-code-review/
|
|
# config.jsonc + routing.jsonc, whose auth.tokenEnv names the env var the CLI
|
|
# forwards as the model credential. The canonical guard ships with the CLI:
|
|
# `ecr verify-config` sweeps every config (root + routing + all scopes, referenced
|
|
# or not) with the engine's real JSONC parser and refuses unless tokenEnv appears
|
|
# exactly once, in a ROOT-owned file, equal to ECR_EXPECTED_TOKEN_ENV — so a
|
|
# base-ref config change can't repoint it at another runner secret, sneak in a
|
|
# JSON-escaped key, or stage an unreferenced scope config with its own auth.
|
|
# This is layer 2; layer 1 is the runtime ECR_EXPECTED_TOKEN_ENV lock in `ecr ci`.
|
|
# Runs after Set up Node so the guard runs the SAME $ECR_VERSION `ecr ci` will.
|
|
# @ref LLP 0009#guard-step-ordering-and-job-budgets [implements] — same $ECR_VERSION feeds guard and review
|
|
- name: Guard config tokenEnv (root + routing + all scopes)
|
|
if: steps.cmd.outputs.run == 'true'
|
|
env:
|
|
# (Comma-separated set for a multi-credential auth.providers config.)
|
|
ECR_EXPECTED_TOKEN_ENV: ${{ vars.ECR_EXPECTED_TOKEN_ENV || 'META_API_KEY' }}
|
|
run: npx --yes -p "@expo/code-review-cli@$ECR_VERSION" ecr verify-config
|
|
|
|
- name: Run AI review
|
|
if: steps.cmd.outputs.run == 'true'
|
|
continue-on-error: true
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
# Layer-1 auth lock: the CLI refuses to run when the tokenEnv it would honor
|
|
# differs from this. Keep it in sync with the guard's EXPECTED.
|
|
ECR_EXPECTED_TOKEN_ENV: ${{ vars.ECR_EXPECTED_TOKEN_ENV || 'META_API_KEY' }}
|
|
# Model credential — the env var named by auth.tokenEnv in config.jsonc.
|
|
# Store each as a repo secret under the same name.
|
|
# Updated per your setting: secret is EXPO_CODE_REVIEW_API_KEY, forwarded as META_API_KEY
|
|
META_API_KEY: ${{ secrets.EXPO_CODE_REVIEW_API_KEY }}
|
|
# Optional search-only credential for trusted platform documentation research.
|
|
BRAVE_SEARCH_API_KEY: ${{ secrets.EXPO_BRAVE_SEARCH_API_KEY }}
|
|
# Optional: override the model for every agent.
|
|
REVIEWER_MODEL: ${{ vars.REVIEWER_MODEL }}
|
|
AGENTS: ${{ steps.cmd.outputs.agents }}
|
|
ROUTE: ${{ steps.cmd.outputs.route }}
|
|
# NOTE: running via `issue_comment` makes this a manual /review, which the CLI
|
|
# detects (GITHUB_EVENT_NAME=issue_comment) and treats as a trigger-gate bypass
|
|
# — it reviews even when the config trigger policy or an `ai-review:skip` label
|
|
# would skip the auto workflow. The bypass affects ONLY the trigger gate; the
|
|
# config guard above, break-glass, and the auth lock still apply.
|
|
run: |
|
|
# Array (not a string) so the flags expand as separate argv entries
|
|
# without unquoted word-splitting. AGENTS is a single sanitized,
|
|
# space-free comma list, so it stays one element.
|
|
ARGS=()
|
|
if [ -n "$AGENTS" ]; then
|
|
ARGS=(--agents "$AGENTS")
|
|
elif [ "$ROUTE" = "true" ]; then
|
|
ARGS=(--route)
|
|
fi
|
|
npx --yes -p "@expo/code-review-cli@$ECR_VERSION" ecr ci "${ARGS[@]}"
|
|
|
|
# Same ephemeral per-run log as the pull_request workflow — a /review command
|
|
# runs the full `ecr ci`, whose .expo-code-review/.runs/ log is gone when the
|
|
# runner tears down. always() captures it even on error, gated on run=='true'
|
|
# (a non-/review comment writes no log); issue.number IS the PR number here
|
|
# (issue_comment context has no pull_request.number).
|
|
- name: Upload review run log
|
|
if: always() && steps.cmd.outputs.run == 'true'
|
|
uses: ./.github/actions/upload-artifact
|
|
with:
|
|
name: review-run-log-pr${{ github.event.issue.number }}
|
|
path: .expo-code-review/.runs/reviews.jsonl
|
|
if-no-files-found: ignore
|
|
retention-days: 14
|