diff --git a/.github/workflows/flamingo-code-review.yml b/.github/workflows/flamingo-code-review.yml new file mode 100644 index 00000000000..b2abbd3df13 --- /dev/null +++ b/.github/workflows/flamingo-code-review.yml @@ -0,0 +1,492 @@ +# Generated by the Flamingo hub β€” do not edit by hand. +# Per-repo settings live in the hub admin (/admin/code-review); this file is +# byte-identical across every reviewed repository AND every deployment, which +# is what makes drift detectable by comparison. The hub's address arrives in +# the dispatch payload (doc-orchestrator style) or, for pull_request runs, the +# org Actions variable FLAMINGO_HUB_BASE_URL. +# Named like its sibling pipeline ('🦩 Flamingo Code Documentation') so both +# read as one product family in the Actions sidebar. +name: 🦩 Flamingo Code Review + +on: + # Push trigger - registers workflow with GitHub Actions (required for + # workflow_dispatch API). Only fires when this file itself changes; the job + # skips it β€” same pattern as the doc-orchestrator workflow. + push: + paths: + - '.github/workflows/flamingo-code-review.yml' + + # ENTERS-REVIEW ONLY (2026-08-12; corrected 2026-09-07). The automatic + # triggers are the moments a pull request ENTERS review, and nothing else. + # + # 'synchronize' (per-push) stays deliberately OFF. Reviewing every commit on + # an open PR is the largest avoidable cost in an AI review pipeline, and it + # trains authors to tune the bot out; the 2026 industry default is to review + # at review moments and offer an explicit re-review on demand. + # + # 'opened' and 'reopened' were OFF between 2026-08-15 and 2026-09-07, on the + # assumption that PRs are opened as drafts and later promoted. They are not + # β€” PRs here are born non-draft, GitHub NEVER fires ready_for_review for + # those, and so the automatic review effectively never ran. Drafts stay + # excluded: an 'opened' event for a draft is dropped by the job's + # draft == false guard, and that PR is reviewed later, on its + # ready_for_review. + # + # 'labeled' is the on-demand re-review β€” the affordance that makes the + # no-per-push default liveable. Commits pushed AFTER the first review are not + # auto-reviewed, so adding the flamingo-review label is how a human + # asks for another pass. A SEPARATE job removes it the moment the event + # arrives, so adding it again asks again, and it can never strand itself on a + # pull request the review gate declines. Every other label name is dropped by + # the job's if β€” a skipped run, zero billable minutes. + # + # A 'synchronize' carrying that label is treated as the same request. GitHub + # runs the workflow from the pull request's HEAD, so a label applied to a + # branch whose head predates this file fires an event no workflow was there + # to handle: the label lands, nothing runs, nothing consumes it, and because + # it is now already present, adding it again fires NOTHING AT ALL. Honouring + # it on the next push is what breaks that stall β€” the request gets served + # once and consumed, instead of sitting on the pull request looking applied. + # + # 'synchronize' (every push) is otherwise here ONLY to serve the + # flamingo-review-always label, and the job's if drops it on every pull + # request that does not carry it. That is the per-PR escalation: subscribe the + # risky refactor to continuous review, leave everything else on one review per + # pull request. A skipped push run costs no billable minutes. + # + # Every repeat pass diffs only the delta since the last reviewed head β€” see + # the incremental anchor in code-review-review.mjs. + pull_request: + types: [opened, ready_for_review, reopened, labeled, synchronize] + repository_dispatch: + types: [flamingo-code-review] + # Lets the hub target a SETUP BRANCH before the install PR merges β€” the same + # test-before-merge flow the doc pipeline uses. repository_dispatch only ever + # fires on the default branch. + workflow_dispatch: + # ═══════════════════════════════════════════════════════════════════════════ + # GENERATED FROM SINGLE SOURCE OF TRUTH: CODE_REVIEW_PARAMS (this module) + # This section is auto-generated when creating workflow PRs + # ═══════════════════════════════════════════════════════════════════════════ + inputs: + run_id: + description: 'Hub run-record id binding this execution to a code_review_runs row. Empty for pull_request runs β€” the hub creates the row from the callback.' + required: false + default: '' + run_token: + description: 'Per-run token minted by the hub at dispatch. Binds the callback to THIS run β€” the org-wide secret alone would let any repo report against another repo’s run.' + required: false + default: '' + mode: + description: 'Review mode: pr (diff-scoped), sweep (whole-repo scanner) or mine (rule mining over the full checkout).' + required: false + default: 'sweep' + hub_base_url: + description: 'Absolute hub origin. Dispatches carry it doc-orchestrator style; pull_request runs fall back to the org/repo Actions variable FLAMINGO_HUB_BASE_URL (shipped by Sync Secrets).' + required: false + default: '' + review_budget_chars: + description: 'Sweep corpus budget in characters for this run (review_config.sweep_budget_chars). Empty = UNCAPPED β€” the whole eligible corpus is reviewed in ~80K batches. The local twin defaults to 80000 instead; set the per-repo value to cap CI spend.' + required: false + default: '' + # ═══════════════════════════════════════════════════════════════════════════ + # END GENERATED SECTION + # ═══════════════════════════════════════════════════════════════════════════ + +# GENERATED FROM SINGLE SOURCE OF TRUTH: CODE_REVIEW_PARAMS (this module) +# The doc-orchestrator's parameter chain for EVERY dispatch param: payload +# first, workflow_dispatch input second, then the param's runtime fallback. +# Workflow-level, so every dispatch var reaches every step and every +# downloaded script. SECURITY: only NON-SENSITIVE values live here β€” secrets +# are passed per-step, same as the doc workflow. +env: + RUN_ID: ${{ github.event.client_payload.run_id || github.event.inputs.run_id || '' }} + MODE: ${{ github.event.client_payload.mode || github.event.inputs.mode || (github.event_name == 'pull_request' && 'pr' || 'sweep') }} + HUB_BASE_URL: ${{ github.event.client_payload.hub_base_url || github.event.inputs.hub_base_url || vars.FLAMINGO_HUB_BASE_URL }} + REVIEW_BUDGET_CHARS: ${{ github.event.client_payload.review_budget_chars || github.event.inputs.review_budget_chars || '' }} + +# Superseding a PR cancels the in-flight review of the stale head SHA; the +# fresh run reviews (and reports on) the new one. The always() report step +# still fires on the cancelled run, so its row records 'cancelled' rather +# than dangling. +concurrency: + group: flamingo-code-review-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + checks: write + +jobs: + # Consuming the label is its OWN job, gated on nothing but "that label was + # added". Living inside the review job meant a label applied to a pull request the + # review gate declines β€” a bot PR, a fork β€” was never consumed: the label sat + # there permanently, and because it was already present, adding it again did + # nothing. A silent dead end with no feedback. Split out, the request is + # always consumed, whether or not a review follows it. + # + # It does NOT remove flamingo-review-always: that label is durable state + # ("keep reviewing this PR"), not a one-shot request. + consume-review-request: + # The fork clause is not a token guard β€” a fork pull_request run gets a + # READ-ONLY token whatever this file declares, so the DELETE below could + # not succeed there even without it. It is here because a job that + # provably cannot do its work should not start: on a fork the label + # strands either way, and burning a runner to fail silently only hides + # that. Same clause, same reason, as the review job below. + if: >- + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + ((github.event.action == 'labeled' && + github.event.label.name == 'flamingo-review') || + (github.event.action == 'synchronize' && + contains(github.event.pull_request.labels.*.name, 'flamingo-review'))) + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Consume the on-demand review label + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO_FULL: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + # Best-effort: losing this race must never fail anything. + run: | + CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG" + printf 'header = "Authorization: Bearer %s"\n' "$GITHUB_TOKEN" > "$CURL_CFG" + curl -sS --max-time 30 -K "$CURL_CFG" -X DELETE \ + "https://api.github.com/repos/$REPO_FULL/issues/$PR_NUMBER/labels/flamingo-review" > /dev/null || true + rm -f "$CURL_CFG" + + review: + # Skip actual work when triggered by push (push only registers the + # workflow with GitHub, which is what lets workflow_dispatch target a + # setup branch before the install PR merges). + # Bots and forks never run, on EVERY event. A fork PR's token is read-only, + # so the run could not post a comment or a check even if it finished; the + # bot clause is what stops a hub-opened ai-fix PR from being reviewed into + # another fix PR, and no label may waive it β€” "only a human applies a + # label" is an assumption, not an invariant, since anything holding + # pull-requests: write can label. To review a bot PR, dispatch it from the + # hub admin, which is an authenticated, recorded decision. + # + # DRAFT is different, and only the on-demand label waives it: an explicit + # request says "review this now" and means it whether or not the PR is + # finished, which is how every comparable tool treats its manual trigger. + # Automatic events and subscribed pushes still skip drafts. + # NOTE: there is deliberately no vars. kill switch here. The hub's + # per-repo enabled dial already covers it and answers 409 REVIEW_DISABLED, + # which produces a recorded run. A second switch living in GitHub would be + # invisible to the admin screen and would produce no callback at all. + if: >- + github.event_name != 'push' && + (github.event_name == 'repository_dispatch' || + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.user.type != 'Bot' && + github.event.pull_request.head.repo.full_name == github.repository && + ((github.event.action == 'labeled' && + github.event.label.name == 'flamingo-review') || + (github.event.action == 'synchronize' && + (contains(github.event.pull_request.labels.*.name, 'flamingo-review') || + (github.event.pull_request.draft == false && + contains(github.event.pull_request.labels.*.name, 'flamingo-review-always')))) || + (github.event.action != 'labeled' && + github.event.action != 'synchronize' && + github.event.pull_request.draft == false)))) + runs-on: ubuntu-latest + # NO custom timeout-minutes β€” deliberately. GitHub's 6h hosted-runner + # ceiling is the only clock: on hitting it the always() report step still + # runs, so a sweep ships its per-batch checkpoint home and the NEXT sweep + # resumes from the covered files (nothing is lost, nothing re-paid). Hung + # runs are the hub reaper's job (run-safety-net-utils.ts, no-report cutoff + # sized past the platform ceiling) β€” a second hand-tuned ceiling here was + # one more number to keep in sync for no added safety. + steps: + # Fail LOUD, not silent: a pull_request run on a repo whose org never set + # FLAMINGO_HUB_BASE_URL would otherwise curl an empty origin and die with + # an unrelated error. Also normalizes a trailing slash ONCE for every + # downstream consumer (a value of https://hub.example/ would otherwise + # yield //api double-slash paths in four places). + - name: Validate configuration + run: | + if [ -z "$HUB_BASE_URL" ]; then + echo "::error::HUB_BASE_URL is empty β€” set the org Actions variable FLAMINGO_HUB_BASE_URL (or pass hub_base_url in the dispatch payload)." + exit 1 + fi + echo "HUB_BASE_URL=${HUB_BASE_URL%/}" >> "$GITHUB_ENV" + + # REPORT CAPABILITY FIRST. Only workflow-helpers.sh + code-review-report.sh + # download here β€” before checkout, cache, or any other script β€” so every + # later failure (including the download step for the REST of the scripts + # 404ing or hash-mismatching, the class that produced phantom 'running' + # rows) still has a verified report script to call home with. + - name: Download the report script + env: + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + HASH_WORKFLOW_HELPERS: "3df9f07c408d987a44e9df7ba1584f01ff57502a596f013dd3218d3b4c30fd4c" + HASH_REPORT: "479b45311e6e91e7dc36b90d6a32db57fae25b215fdcd35c2889fb5f0618df07" + run: | + set -euo pipefail + SCRIPTS_BASE_URL="${HUB_BASE_URL}/api/doc-orchestrator/scripts" + + download_and_verify() { + local script_name="$1" + local expected_hash="$2" + local output_path="/tmp/$script_name" + + curl -fsSL "$SCRIPTS_BASE_URL/$script_name" \ + -H "Authorization: Bearer $WEBHOOK_SECRET" \ + -o "$output_path" + + local actual_hash=$(shasum -a 256 "$output_path" | cut -d' ' -f1) + + if [ "$actual_hash" != "$expected_hash" ]; then + echo "❌ HASH MISMATCH for $script_name!" + echo " Expected: $expected_hash" + echo " Actual: $actual_hash" + echo " This could indicate tampering or an outdated hash." + exit 1 + fi + + # Make shell scripts executable + if [[ "$script_name" == *.sh ]]; then + chmod +x "$output_path" + fi + + echo "βœ… $script_name verified (hash: ${actual_hash:0:16}...)" + } + + download_and_verify "workflow-helpers.sh" "$HASH_WORKFLOW_HELPERS" + download_and_verify "code-review-report.sh" "$HASH_REPORT" + + # Early liveness ping β€” the FIRST real step after report capability is + # secured. Stamps workflow_run_id + status 'running' on the hub's run row, + # so "dispatch accepted but nothing ever ran" (no ping β€” reaped fast) is + # distinguishable from "started, then crashed" (pinged β€” longer deadline). + # Best-effort by design: the script exits 0 regardless; the always() + # report below is the authoritative callback. + - name: Tell the hub the run started + env: + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + RUN_TOKEN: ${{ github.event.client_payload.run_token || github.event.inputs.run_token || '' }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + WF_RUN_ID: ${{ github.run_id }} + REPO_FULL: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: /tmp/code-review-report.sh --started + + # v5 = the Node 24 drop-in (v4 targets EOL Node 20 and warns on every run). + - name: Check out the code under review + uses: actions/checkout@v5 + with: + fetch-depth: 0 + # The review job must never hold a push credential. + persist-credentials: false + + # The corpus hash has to SURVIVE between runs, or the hash param is empty + # and the 304 path the hub implements is unreachable β€” the corpus would be + # transferred in full on every run in both modes. + # v5 = the Node 24 drop-in (v4 targets EOL Node 20 and warns on every run). + - name: Restore the last corpus hash + uses: actions/cache@v5 + with: + # BOTH files. Caching only the hash meant a 304 left rules.json + # truncated to zero bytes while the run continued as if it had a + # corpus. + path: | + .rules-hash + rules.json + key: flamingo-rules-hash-${{ github.repository }}-${{ github.run_id }} + restore-keys: | + flamingo-rules-hash-${{ github.repository }}- + + # The SAME download_and_verify function as the doc-orchestrator workflow + # (single source: lib/config/workflow-scripts-bootstrap.ts, parity with + # the doc template asserted at build time), fetching from the SAME + # scripts endpoint. This bootstrap (and the report-capability step above, + # which shares it) is the ONLY inline bash in the file β€” every other step + # runs a verified unified script, so a script change ships from the hub + # without touching this caller. Helpers + report already downloaded above; + # this step fetches the rest, and its failure is REPORTABLE. + - name: Download and verify scripts + env: + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + HASH_FETCH_RULES: "a3adb1976b782a6bfe405b68c7adc52f6650535e0d9fbc2ea427c3567f2c9943" + HASH_RUN: "e4ebfe017a09ed3f783c8aea995a079be46b4add3b3f92db1918291d1b3e7412" + HASH_MINE: "c12e6fd532bab44c95e855046efef3b45940fbde87d449d5577d198a0d3c4f62" + HASH_REVIEW: "b81f744cf26dc73a343d5fd14cd7cd40a45da4f7a4e62ac675a66ab5178e266a" + HASH_LIB: "adeba0f5d1c68ee2531e6c23f4a2ec206f3dc33f67a2b8228ade99509c6a6149" + HASH_POST: "75404d6cf735f9a9f1332e65efb6e055ccaf18fe678b1fb3d74d0b0b996c6f2d" + run: | + set -euo pipefail + SCRIPTS_BASE_URL="${HUB_BASE_URL}/api/doc-orchestrator/scripts" + + download_and_verify() { + local script_name="$1" + local expected_hash="$2" + local output_path="/tmp/$script_name" + + curl -fsSL "$SCRIPTS_BASE_URL/$script_name" \ + -H "Authorization: Bearer $WEBHOOK_SECRET" \ + -o "$output_path" + + local actual_hash=$(shasum -a 256 "$output_path" | cut -d' ' -f1) + + if [ "$actual_hash" != "$expected_hash" ]; then + echo "❌ HASH MISMATCH for $script_name!" + echo " Expected: $expected_hash" + echo " Actual: $actual_hash" + echo " This could indicate tampering or an outdated hash." + exit 1 + fi + + # Make shell scripts executable + if [[ "$script_name" == *.sh ]]; then + chmod +x "$output_path" + fi + + echo "βœ… $script_name verified (hash: ${actual_hash:0:16}...)" + } + + download_and_verify "code-review-fetch-rules.sh" "$HASH_FETCH_RULES" + download_and_verify "code-review-run.sh" "$HASH_RUN" + download_and_verify "code-review-mine.mjs" "$HASH_MINE" + download_and_verify "code-review-review.mjs" "$HASH_REVIEW" + download_and_verify "code-review-lib.mjs" "$HASH_LIB" + download_and_verify "code-review-post.mjs" "$HASH_POST" + + - name: Fetch the rule corpus + id: rules + env: + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + # PR runs identify the pull request so the hub can answer with the + # last successfully reviewed head SHA (X-Last-Reviewed-Sha) β€” the + # incremental-review anchor persisted as .last-reviewed-sha. + PR_NUMBER: ${{ github.event.pull_request.number }} + run: /tmp/code-review-fetch-rules.sh + + # Stage checkpoints are their OWN credentialed steps β€” the review step + # deliberately never holds the webhook secret or run token (it processes + # untrusted PR content), so pings happen at step boundaries, not inside. + - name: Report progress β€” reviewing + env: + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + RUN_TOKEN: ${{ github.event.client_payload.run_token || github.event.inputs.run_token || '' }} + WF_RUN_ID: ${{ github.run_id }} + REPO_FULL: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: /tmp/code-review-report.sh --progress review + + - name: Review + # The id lets the report step read the reviewer's own degraded output + # (e.g. skipped_trivial_diff) alongside the rules step's. + id: review + if: steps.rules.outputs.skip != 'true' + # MODE / RUN_ID arrive via the generated workflow-level env; only the + # secret is scoped to the step (doc-workflow security discipline). + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + run: /tmp/code-review-run.sh + + # Post the findings to the PULL REQUEST itself and publish the + # 'Flamingo Code Review' check run. pull_request events only β€” a sweep + # has no PR to post to (its findings live in the hub dashboard). Uses the + # runner's own GITHUB_TOKEN under the workflow's minimal grant + # (pull-requests: write for the comments, checks: write for the check + # run). One UPSERTED summary comment + inline comments where the diff + # supports them; already-posted fingerprints are never re-commented, so + # a new push adds only NEW findings. The check run is 'neutral' unless + # the repo's served mode is 'blocking' AND action_required findings + # exist. Writes posted.json (fingerprint -> comment id) for the report + # step's callback β€” the reaction-learning loop reads it. + - name: Report progress β€” finalizing + env: + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + RUN_TOKEN: ${{ github.event.client_payload.run_token || github.event.inputs.run_token || '' }} + WF_RUN_ID: ${{ github.run_id }} + REPO_FULL: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: /tmp/code-review-report.sh --progress post + + - name: Post to the pull request + id: post + if: github.event_name == 'pull_request' && steps.rules.outputs.skip != 'true' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEGRADED: ${{ steps.review.outputs.degraded }} + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: node /tmp/code-review-post.mjs + + # A crashed Review step must still leave a trace ON THE PR: default + # step-skip semantics silently skip the post step on failure, so the + # developer saw no signal at all that a review was attempted β€” the + # failure lived only on the hub's internal dashboard. Best-effort (|| + # true): a failed courtesy comment must not mask the real failure state. + - name: Post crash notice to the pull request + if: failure() && github.event_name == 'pull_request' && steps.review.outcome == 'failure' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + REPO_FULL: ${{ github.repository }} + WF_RUN_ID: ${{ github.run_id }} + run: | + BODY="🦩 **Flamingo Code Review crashed on this push.** No findings were posted β€” this is an infrastructure failure, not a clean review. See the [workflow run](https://github.com/$REPO_FULL/actions/runs/$WF_RUN_ID)." + CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG" + printf 'header = "Authorization: Bearer %s"\n' "$GITHUB_TOKEN" > "$CURL_CFG" + curl -s -K "$CURL_CFG" -X POST "https://api.github.com/repos/$REPO_FULL/issues/$PR_NUMBER/comments" \ + -H "Content-Type: application/json" \ + --data "$(printf '%s' "$BODY" | jq -Rs '{body: .}')" > /dev/null || true + rm -f "$CURL_CFG" + + # The report script downloads FIRST (dedicated step above), so a missing + # /tmp/code-review-report.sh here means the report-capability bootstrap + # itself failed (endpoint down, hash mismatch before anything else ran). + # The guarded inline curl below still posts a minimal failure callback for + # exactly that case; the hub's tiered reaper remains the last resort. + - name: Report back to the hub + if: always() && env.HUB_BASE_URL != '' + # RUN_ID / MODE / HUB_BASE_URL arrive via the generated workflow-level + # env. RUN_TOKEN is SENSITIVE (per-run bearer) so it is step-scoped + # HERE β€” the only consumer β€” and masked by the script; the review step + # that processes untrusted PR content never sees it. Remaining step env + # is the secret plus values that exist nowhere but the GitHub context β€” + # which enters through env, never interpolated into a script body. + env: + WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }} + RUN_TOKEN: ${{ github.event.client_payload.run_token || github.event.inputs.run_token || '' }} + JOB_STATUS: ${{ job.status }} + # Either step can degrade a run: the rules step (corpus unavailable) + # or the reviewer itself (trivial-diff skip, unresolvable PR base). + DEGRADED: ${{ steps.rules.outputs.degraded || steps.review.outputs.degraded }} + # Dispatch events carry no pull_request context β€” github.sha is the + # tip of the checked-out ref, so sweep runs record the commit too. + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + WF_RUN_ID: ${{ github.run_id }} + REPO_FULL: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number }} + # The ONE justified inline-bash exception beyond the bootstrap: when the + # report script itself never downloaded, no unified script exists to + # report the failure β€” so a minimal guarded curl posts it. Kept tiny on + # purpose; all real reporting logic stays in code-review-report.sh. + run: | + if [ ! -x /tmp/code-review-report.sh ]; then + echo "::error::code-review-report.sh missing β€” sending bootstrap-failure callback" + # Secrets NEVER ride argv (ps-visible): the bearer goes through a + # 0600 curl config file and RUN_TOKEN through jq's env β€” the same + # transport rule the report script itself follows. + CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG" + printf 'header = "Authorization: Bearer %s"\n' "$WEBHOOK_SECRET" > "$CURL_CFG" + jq -n --arg run_id "${RUN_ID:-}" \ + --arg wf "$WF_RUN_ID" --arg repo "$REPO_FULL" --arg mode "$MODE" \ + '{run_id: $run_id, run_token: ($ENV.RUN_TOKEN // ""), status: "failure", + degraded_reason: "bootstrap_failed", workflow_run_id: $wf, + repo_full_name: $repo, mode: $mode, + error_message: "Script bootstrap failed: the report script never downloaded from the hub."}' | + curl -sS --max-time 30 -K "$CURL_CFG" -X POST "$HUB_BASE_URL/api/code-review/webhook" \ + -H "Content-Type: application/json" -d @- || true + rm -f "$CURL_CFG" + exit 1 + fi + /tmp/code-review-report.sh