Skip to content

🦩 Update: Flamingo Code Documentation #111

🦩 Update: Flamingo Code Documentation

🦩 Update: Flamingo Code Documentation #111

# 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]
# THE PRIMARY MANUAL TRIGGER (2026-09-08). A new top-level pull-request
# comment beginning with '@flamingo-review' asks for another pass;
# '@flamingo-review full' additionally overrules the incremental anchor
# and re-reads the whole cumulative diff.
#
# This exists because the label could not TEACH ITSELF. The summary comment
# is the only surface an author reliably sees, a comment cannot link to or
# explain a sidebar label, and so the once-per-PR default read as "the bot
# only reviewed my first commit". A command can be printed in the very
# comment that prompts the question — see the footer in code-review-post.mjs.
# It is also where the market landed: CodeRabbit ('@coderabbitai review' /
# 'full review') and Cursor Bugbot ('cursor review' / 'bugbot run') both take
# a new top-level comment, and both split incremental from full.
#
# 'edited' is the CHECKBOX lane — the closest thing a GitHub comment has to a
# button, and the same mechanism CodeRabbit uses for its clickable actions.
# Checking a task-list box in the summary comment edits that comment, which
# fires issue_comment.edited; the boxes are the two commands, clickable.
#
# Its costs are real and accepted, not overlooked: 'edited' fires on EVERY
# comment edit in the repository, so an unrelated typo fix evaluates this
# workflow and skips (zero billable minutes, but a skipped run in the Actions
# list — the same trade already made for push and synchronize). And a
# checkbox is STATE, so resolve_command unchecks it after consuming, which is
# what lets it be clicked again.
#
# WRITE ACCESS IS ENFORCED BY GITHUB HERE, not by us: only a user with write
# permission can toggle a task list in someone else's comment, so the
# author_association gate that gutters the 'created' lane has no equivalent —
# and needs none. What IS checked is that the edited comment is the bot's own
# summary and that a human did the editing, so the uncheck below cannot
# re-trigger the workflow it just served.
#
# issue_comment fires only for top-level comments; a reply inside a review
# thread is pull_request_review_comment and is NOT a trigger here (the same
# boundary Bugbot documents). Review-thread replies stay conversation.
#
# SECURITY, and the reason for the resolve_command job below: unlike a
# pull_request run, an issue_comment run executes in the BASE repository with
# a WRITE token and full secrets. The event carries no head SHA and no head
# repository, so 'is this a fork?' cannot be answered in a job 'if' — and
# checking out refs/pull/N/head without answering it would run this workflow
# over fork-authored code holding that token. resolve_command answers it via
# the API BEFORE anything is checked out, and forks are refused there.
issue_comment:
types: [created, edited]
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 80000-char 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' || github.event_name == 'issue_comment') && '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 || '' }}
# Concurrency sits on the REVIEW JOB (below), not the workflow: a
# workflow-level group is joined when the RUN is queued, before any job if:,
# so a human replying to the "Running…" placeholder (issue_comment.created,
# same PR number) or any label event cancelled the in-flight review and then
# skipped every job — nothing re-reviewed. A skipped job never joins a
# job-level group.
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"
trap 'rm -f "$CURL_CFG"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$GITHUB_TOKEN" > "$CURL_CFG"
curl -sS --max-time 30 -K "$CURL_CFG" -X DELETE \
"${GITHUB_API_URL:-https://api.github.com}/repos/$REPO_FULL/issues/$PR_NUMBER/labels/flamingo-review" > /dev/null || true
# Resolves a COMMENT COMMAND into the pull-request context the review job
# needs, and refuses the cases the job 'if' provably cannot judge.
#
# It exists because an issue_comment event is context-poor by design: it
# carries the comment and the ISSUE, never the pull request. No head SHA, no
# head repository, no draft flag, no author type. Everything the review job's
# guard reads from github.event.pull_request.* is simply absent, and the two
# facts that gate SAFETY — is this a fork, is the author a bot — can only be
# answered by asking the API. A job 'if' cannot make an API call, so this job
# is the 'if' that could not be written as one.
#
# Ordering is the point: nothing is checked out until after the fork answer.
# An issue_comment run holds a WRITE token, so checking out refs/pull/N/head
# first and asking afterwards would have already run this workflow over
# fork-authored code with that token in scope.
#
# The cheap, event-local half of the gate stays in the 'if' below (a
# command-shaped comment, on an open pull request, from someone with write
# access) so an outsider's comment never starts a runner at all.
#
# 'github.event.issue.pull_request' is tested for TRUTHINESS, never against
# null. Comparing an object to null in an Actions expression coerces both to
# numbers, the object becomes NaN, and every NaN comparison is false — so the
# null form would have refused every real pull request while looking correct.
#
# Underscored job id ON PURPOSE: 'needs.<id>' is a context path, and a hyphen
# there parses as subtraction. A hyphenated id would need needs['...'] at
# every one of the reads below, and the first person to add a plain one would
# get a silently-empty value rather than an error.
resolve_command:
if: >-
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.issue.state == 'open' &&
((github.event.action == 'created' &&
startsWith(github.event.comment.body, '@flamingo-review') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event.action == 'edited' &&
github.event.comment.user.type == 'Bot' &&
github.event.sender.type != 'Bot' &&
(contains(github.event.comment.body, '[x] Review the new commits') ||
contains(github.event.comment.body, '[x] Review the whole diff again'))))
runs-on: ubuntu-latest
permissions:
pull-requests: write
outputs:
# The review job reads ONLY 'proceed'. Every refusal is a false here plus
# an explanation posted to the pull request — never a red run, because a
# command this pipeline declines to serve is not a broken pipeline, and a
# failed check on someone's PR for asking a question is noise.
proceed: ${{ steps.resolve.outputs.proceed }}
pr_number: ${{ steps.resolve.outputs.pr_number }}
head_sha: ${{ steps.resolve.outputs.head_sha }}
base_ref: ${{ steps.resolve.outputs.base_ref }}
full: ${{ steps.resolve.outputs.full }}
steps:
- name: Resolve the pull request behind the comment
id: resolve
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO_FULL: ${{ github.repository }}
PR_NUMBER: ${{ github.event.issue.number }}
COMMENT_ID: ${{ github.event.comment.id }}
# 'created' (a typed command) vs 'edited' (a checked box) — the two
# lanes read the request from different places in the same body.
EVENT_ACTION: ${{ github.event.action }}
# Read as DATA, never interpolated into the script body: a comment is
# attacker-authored text, and ${{ }}-ing it into 'run:' is the
# canonical Actions script-injection sink. The full-pass test below is
# a shell string comparison against this variable.
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG"
trap 'rm -f "$CURL_CFG"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$GITHUB_TOKEN" > "$CURL_CFG"
# 'proceed' is written ONCE, at the end, and only on success. Every
# other exit — a refusal, or the script dying under set -e — leaves it
# unset, which the review job reads as an empty string and declines.
# Fail-closed by construction rather than by remembering to write a
# false on every path out.
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
# One acknowledgement, immediately: a command that produces no visible
# effect for the ~40s before the review job posts anything is
# indistinguishable from one that was never picked up, and the second
# thing a human does about that is ask again.
curl -sS --max-time 30 -K "$CURL_CFG" -X POST \
-H "Accept: application/vnd.github+json" \
"${GITHUB_API_URL:-https://api.github.com}/repos/$REPO_FULL/issues/comments/$COMMENT_ID/reactions" \
-d '{"content":"eyes"}' > /dev/null || true
PR_JSON=$(curl -sS --max-time 30 -K "$CURL_CFG" \
-H "Accept: application/vnd.github+json" \
"${GITHUB_API_URL:-https://api.github.com}/repos/$REPO_FULL/pulls/$PR_NUMBER") || PR_JSON=""
# Each read tolerates a body that is not JSON at all (an HTML error
# page from a proxy, a truncated response). Without the '|| true' set
# -e would kill the script mid-parse, and a script that dies here
# emits no 'proceed' AND no explanation — the command would look
# ignored, which is the exact failure this whole change exists to
# remove. An empty HEAD_SHA falls into the refusal below instead.
HEAD_SHA=$(printf '%s' "$PR_JSON" | jq -r '.head.sha // empty' 2>/dev/null || true)
HEAD_REPO=$(printf '%s' "$PR_JSON" | jq -r '.head.repo.full_name // empty' 2>/dev/null || true)
# The BASE the review must diff against. An issue_comment carries no
# GITHUB_BASE_REF (that variable exists only on pull_request events),
# and the reviewer's remaining fallbacks all need origin/HEAD or an
# authenticated 'git remote show' — neither of which a runner has. So
# every commanded review died as 'pr_base_unresolvable' while the
# answer was sitting in the pull request payload we already fetched.
BASE_REF=$(printf '%s' "$PR_JSON" | jq -r '.base.ref // empty' 2>/dev/null || true)
AUTHOR_TYPE=$(printf '%s' "$PR_JSON" | jq -r '.user.type // empty' 2>/dev/null || true)
refuse() {
# Say why, in the thread that asked. A silent no-op here is the
# exact failure mode the label had.
printf '%s' "$1" | jq -Rs '{body: .}' | \
curl -sS --max-time 30 -K "$CURL_CFG" -X POST \
-H "Accept: application/vnd.github+json" \
"${GITHUB_API_URL:-https://api.github.com}/repos/$REPO_FULL/issues/$PR_NUMBER/comments" \
-d @- > /dev/null || true
exit 0
}
if [ -z "$HEAD_SHA" ]; then
refuse "🦩 Could not read this pull request from the GitHub API, so the review was not started. Try the command again."
fi
# The SAME two exclusions the review job applies to every other
# event, restated here because the job 'if' had nothing to read them
# from. A fork additionally cannot be waived by any command: this run
# holds a write token.
if [ "$HEAD_REPO" != "$REPO_FULL" ]; then
refuse "🦩 This pull request comes from a fork, so it is not reviewed automatically. Dispatch it from the Flamingo hub instead."
fi
if [ "$AUTHOR_TYPE" = "Bot" ]; then
refuse "🦩 This pull request was opened by a bot, so it is not reviewed automatically. Dispatch it from the Flamingo hub instead."
fi
# DRAFTS ARE ALLOWED — same rule the on-demand label already follows:
# an explicit request means it whether or not the PR is finished.
# Which of the two passes was asked for, in whichever lane asked.
# A TYPED command is a prefix test; a CHECKED BOX is a substring test,
# because the box sits inside a whole rendered comment.
FULL=false
case "$EVENT_ACTION" in
edited)
case "$COMMENT_BODY" in
*"[x] Review the whole diff again"*) FULL=true ;;
esac
;;
*)
case "$COMMENT_BODY" in
'@flamingo-review full'*) FULL=true ;;
esac
;;
esac
# RE-ARM the checkbox by unchecking it. Without this the box stays
# checked until the next review overwrites the whole comment, so it
# reads as "already requested" and cannot be clicked again — and on a
# refused request it would stay checked forever, which is exactly the
# stranded-state failure the on-demand label was split into its own
# job to avoid.
#
# Best-effort: a review that ran is worth more than a tidy checkbox,
# and this edit is made by the workflow token, whose events never
# trigger workflows — so it cannot loop back into this job.
if [ "$EVENT_ACTION" = "edited" ]; then
printf '%s' "$COMMENT_BODY" \
| sed -e 's/\[x\] Review the new commits/[ ] Review the new commits/' \
-e 's/\[x\] Review the whole diff again/[ ] Review the whole diff again/' \
| jq -Rs '{body: .}' \
| curl -sS --max-time 30 -K "$CURL_CFG" -X PATCH \
-H "Accept: application/vnd.github+json" \
"${GITHUB_API_URL:-https://api.github.com}/repos/$REPO_FULL/issues/comments/$COMMENT_ID" \
-d @- > /dev/null || true
fi
{
echo "proceed=true"
echo "head_sha=$HEAD_SHA"
echo "base_ref=$BASE_REF"
echo "full=$FULL"
} >> "$GITHUB_OUTPUT"
review:
# 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 job, so its row records 'cancelled' rather
# than dangling. Job-level on purpose — see the note above permissions.
concurrency:
group: flamingo-code-review-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }}
cancel-in-progress: true
# 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.
#
# A COMMENT COMMAND arrives here already judged: resolve_command made the
# API calls this expression cannot, and its 'proceed' carries the same two
# exclusions (fork, bot) plus the "is it really a pull request" answer.
#
# 'needs' therefore requires always(): on every OTHER event resolve_command
# is skipped, and a skipped dependency would otherwise skip this job too —
# i.e. adding the comment trigger would have silently disabled every
# existing trigger. always() re-admits them; the clauses below still decide.
needs: [resolve_command]
if: >-
always() &&
github.event_name != 'push' &&
(github.event_name == 'repository_dispatch' ||
needs.resolve_command.outputs.proceed == 'true' ||
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 }}
run: |
set -euo pipefail
SCRIPT_MANIFEST=/tmp/flamingo-script-manifest.json
# WEBHOOK_SECRET reaches curl through a 0600 config file, never argv — see
# curlAuthPreamble, which always traps the removal.
CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG"
trap 'rm -f "$CURL_CFG"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$WEBHOOK_SECRET" > "$CURL_CFG"
# The canonical scripts surface and the pre-rename one. load_script_manifest
# picks whichever this deployment actually serves and pins SCRIPTS_BASE_URL.
CI_SCRIPTS_URL="${HUB_BASE_URL%/}/api/ci/scripts"
LEGACY_SCRIPTS_URL="${HUB_BASE_URL%/}/api/doc-orchestrator/scripts"
# _try_manifest <base> — 0 loaded, 1 no manifest surface there, 2 fatal.
# The manifest is asked for ONE group: its keys are the files to download.
_try_manifest() {
local base="$1" code
code=$(curl -sS -w '%{http_code}' -o "$SCRIPT_MANIFEST" \
-K "$CURL_CFG" \
"$base/manifest.json?group=$SCRIPT_GROUP") || code="000"
if [ "$code" = "404" ]; then rm -f "$SCRIPT_MANIFEST"; return 1; fi
if [ "$code" != "200" ]; then
echo "❌ manifest request to $base failed (HTTP $code)"
rm -f "$SCRIPT_MANIFEST"
return 2
fi
# The digests are the TOP-LEVEL object. successResponse is the standard
# emitter but it does NOT add a wrapper — it is NextResponse.json(data)
# plus the no-store header — so there is no .data to reach through.
# A 200 that is not a manifest is how a hub which does not serve this path
# answers (the proxy rewrites unknown routes and returns HTML), so it
# means "wrong surface", not "corrupt".
if ! jq -e 'type == "object" and length > 0 and (to_entries | all(.value | type == "string"))' "$SCRIPT_MANIFEST" >/dev/null 2>&1; then
rm -f "$SCRIPT_MANIFEST"
return 1
fi
return 0
}
# load_script_manifest <group>
load_script_manifest() {
SCRIPT_GROUP="$1"
# The scripts surface was renamed from /api/doc-orchestrator/scripts to the
# pipeline-neutral /api/ci/scripts (it always served BOTH pipelines). The
# workflow file ships in the repo and the routes ship with the deployment,
# so the two are one version apart in BOTH directions across the rollout.
# Probe the canonical surface, fall back to the legacy one, and let the
# winner decide SCRIPTS_BASE_URL for every download that follows.
# "cmd; rc=$?" dies under the set -euo pipefail these steps run with —
# errexit fires before rc is read and the step ends with NO output. And
# "if ! cmd; then rc=$?" is worse: inside the branch $? is the status of
# the NEGATION (0), so every failure reads as success. "|| rc=$?" is the
# one form that both suppresses errexit and preserves the real code.
local rc=0
_try_manifest "$CI_SCRIPTS_URL" || rc=$?
if [ "$rc" = "0" ]; then
SCRIPTS_BASE_URL="$CI_SCRIPTS_URL"
echo "✅ script manifest loaded ($(jq -r 'length' "$SCRIPT_MANIFEST") scripts)"
return 0
fi
if [ "$rc" = "2" ]; then exit 1; fi
echo "::warning::this hub does not serve $CI_SCRIPTS_URL — falling back to the legacy $LEGACY_SCRIPTS_URL; it predates the rename"
SCRIPTS_BASE_URL="$LEGACY_SCRIPTS_URL"
rc=0
_try_manifest "$LEGACY_SCRIPTS_URL" || rc=$?
if [ "$rc" = "0" ]; then
echo "✅ script manifest loaded ($(jq -r 'length' "$SCRIPT_MANIFEST") scripts)"
return 0
fi
if [ "$rc" = "2" ]; then exit 1; fi
# Neither surface published a manifest: a hub older than the manifest
# itself. The manifest is the file list, so there is nothing to download.
echo "❌ no script manifest on this hub ($HUB_BASE_URL): it cannot name the $SCRIPT_GROUP scripts. Redeploy the hub."
exit 1
}
# download_script_group <group> — the hub names the files, this workflow
# names only the group. Downloads every script of the group, in served order.
download_script_group() {
load_script_manifest "$1"
local name
# The loop runs in THIS shell (no pipe), so a failed download exits the step.
while IFS= read -r name; do
download_and_verify "$name"
done < <(jq -r 'keys_unsorted[]' "$SCRIPT_MANIFEST")
}
download_and_verify() {
local script_name="$1"
local output_path="/tmp/$script_name"
local expected_hash
expected_hash=$(jq -r --arg n "$script_name" '.[$n] // empty' "$SCRIPT_MANIFEST")
if [ -z "$expected_hash" ]; then
echo "❌ $script_name is not in the server's script manifest!"
echo " The hub serves no such script, or it failed to read on the server."
exit 1
fi
if ! printf '%s' "$expected_hash" | grep -Eq '^[0-9a-f]{64}$'; then
echo "❌ the manifest entry for $script_name is not a SHA-256 digest — refusing to run it."
exit 1
fi
curl -fsSL "$SCRIPTS_BASE_URL/$script_name" \
-K "$CURL_CFG" \
-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 " The download was corrupted in transit — both values come from the same deployment."
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_script_group "review-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 || needs.resolve_command.outputs.head_sha || github.sha }}
WF_RUN_ID: ${{ github.run_id }}
REPO_FULL: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || needs.resolve_command.outputs.pr_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
# A pull_request run is already checked out at the right ref, and a
# dispatch wants the branch it targeted — both leave this EMPTY,
# which is checkout's own "use the triggering ref" default.
#
# An issue_comment run is the exception and the reason this line
# exists: that event's ref is the DEFAULT BRANCH, not the pull
# request. Without an explicit ref the command would cheerfully
# review main and report the result onto someone's PR.
#
# Pinned to the SHA resolve_command read, not to refs/pull/N/head:
# the sha is what the run reports on, what the incremental anchor is
# compared against, and what the check lands on. Resolving the ref a
# second time here could pick up a push that landed in between and
# review a different commit than the one recorded.
ref: ${{ needs.resolve_command.outputs.head_sha }}
# The review job must never hold a push credential.
persist-credentials: false
# 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 the review scripts from the hub
id: scripts
env:
WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }}
run: |
set -euo pipefail
SCRIPT_MANIFEST=/tmp/flamingo-script-manifest.json
# WEBHOOK_SECRET reaches curl through a 0600 config file, never argv — see
# curlAuthPreamble, which always traps the removal.
CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG"
trap 'rm -f "$CURL_CFG"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$WEBHOOK_SECRET" > "$CURL_CFG"
# The canonical scripts surface and the pre-rename one. load_script_manifest
# picks whichever this deployment actually serves and pins SCRIPTS_BASE_URL.
CI_SCRIPTS_URL="${HUB_BASE_URL%/}/api/ci/scripts"
LEGACY_SCRIPTS_URL="${HUB_BASE_URL%/}/api/doc-orchestrator/scripts"
# _try_manifest <base> — 0 loaded, 1 no manifest surface there, 2 fatal.
# The manifest is asked for ONE group: its keys are the files to download.
_try_manifest() {
local base="$1" code
code=$(curl -sS -w '%{http_code}' -o "$SCRIPT_MANIFEST" \
-K "$CURL_CFG" \
"$base/manifest.json?group=$SCRIPT_GROUP") || code="000"
if [ "$code" = "404" ]; then rm -f "$SCRIPT_MANIFEST"; return 1; fi
if [ "$code" != "200" ]; then
echo "❌ manifest request to $base failed (HTTP $code)"
rm -f "$SCRIPT_MANIFEST"
return 2
fi
# The digests are the TOP-LEVEL object. successResponse is the standard
# emitter but it does NOT add a wrapper — it is NextResponse.json(data)
# plus the no-store header — so there is no .data to reach through.
# A 200 that is not a manifest is how a hub which does not serve this path
# answers (the proxy rewrites unknown routes and returns HTML), so it
# means "wrong surface", not "corrupt".
if ! jq -e 'type == "object" and length > 0 and (to_entries | all(.value | type == "string"))' "$SCRIPT_MANIFEST" >/dev/null 2>&1; then
rm -f "$SCRIPT_MANIFEST"
return 1
fi
return 0
}
# load_script_manifest <group>
load_script_manifest() {
SCRIPT_GROUP="$1"
# The scripts surface was renamed from /api/doc-orchestrator/scripts to the
# pipeline-neutral /api/ci/scripts (it always served BOTH pipelines). The
# workflow file ships in the repo and the routes ship with the deployment,
# so the two are one version apart in BOTH directions across the rollout.
# Probe the canonical surface, fall back to the legacy one, and let the
# winner decide SCRIPTS_BASE_URL for every download that follows.
# "cmd; rc=$?" dies under the set -euo pipefail these steps run with —
# errexit fires before rc is read and the step ends with NO output. And
# "if ! cmd; then rc=$?" is worse: inside the branch $? is the status of
# the NEGATION (0), so every failure reads as success. "|| rc=$?" is the
# one form that both suppresses errexit and preserves the real code.
local rc=0
_try_manifest "$CI_SCRIPTS_URL" || rc=$?
if [ "$rc" = "0" ]; then
SCRIPTS_BASE_URL="$CI_SCRIPTS_URL"
echo "✅ script manifest loaded ($(jq -r 'length' "$SCRIPT_MANIFEST") scripts)"
return 0
fi
if [ "$rc" = "2" ]; then exit 1; fi
echo "::warning::this hub does not serve $CI_SCRIPTS_URL — falling back to the legacy $LEGACY_SCRIPTS_URL; it predates the rename"
SCRIPTS_BASE_URL="$LEGACY_SCRIPTS_URL"
rc=0
_try_manifest "$LEGACY_SCRIPTS_URL" || rc=$?
if [ "$rc" = "0" ]; then
echo "✅ script manifest loaded ($(jq -r 'length' "$SCRIPT_MANIFEST") scripts)"
return 0
fi
if [ "$rc" = "2" ]; then exit 1; fi
# Neither surface published a manifest: a hub older than the manifest
# itself. The manifest is the file list, so there is nothing to download.
echo "❌ no script manifest on this hub ($HUB_BASE_URL): it cannot name the $SCRIPT_GROUP scripts. Redeploy the hub."
exit 1
}
# download_script_group <group> — the hub names the files, this workflow
# names only the group. Downloads every script of the group, in served order.
download_script_group() {
load_script_manifest "$1"
local name
# The loop runs in THIS shell (no pipe), so a failed download exits the step.
while IFS= read -r name; do
download_and_verify "$name"
done < <(jq -r 'keys_unsorted[]' "$SCRIPT_MANIFEST")
}
download_and_verify() {
local script_name="$1"
local output_path="/tmp/$script_name"
local expected_hash
expected_hash=$(jq -r --arg n "$script_name" '.[$n] // empty' "$SCRIPT_MANIFEST")
if [ -z "$expected_hash" ]; then
echo "❌ $script_name is not in the server's script manifest!"
echo " The hub serves no such script, or it failed to read on the server."
exit 1
fi
if ! printf '%s' "$expected_hash" | grep -Eq '^[0-9a-f]{64}$'; then
echo "❌ the manifest entry for $script_name is not a SHA-256 digest — refusing to run it."
exit 1
fi
curl -fsSL "$SCRIPTS_BASE_URL/$script_name" \
-K "$CURL_CFG" \
-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 " The download was corrupted in transit — both values come from the same deployment."
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}...)"
}
# Digests come from the deployment that serves the bytes, never from
# this file: a pinned hash here would be the PR head ref's while the
# scripts are main's, which is exactly how a PR used to brick its own
# review. See workflow-scripts-bootstrap.ts.
#
# The FILE LIST is the hub's too: this step names a group and downloads
# every script the manifest lists for it (SCRIPT_GROUPS in
# lib/config/ci-script-catalog.ts), so a module added to the group
# reaches this repo on its next run with no workflow re-push.
download_script_group "review"
# The graph library rides in the same group, and is OPTIONAL: the
# reviewer imports it dynamically and reviews without cross-repo facts
# when it is absent. Whether it came is read from the manifest that
# was just served (never from /tmp, which a self-hosted runner keeps
# between jobs). The graph_lib output gates the dependency
# install below, so a review without the library installs nothing.
if jq -e 'has("code-graph-lib.mjs")' "$SCRIPT_MANIFEST" >/dev/null 2>&1; then
echo "graph_lib=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::this hub does not serve the code-graph library; reviewing without cross-repo facts"
echo "graph_lib=false" >> "$GITHUB_OUTPUT"
fi
# ONE hub call answers how this repository is reviewed: the settings
# (mode, models, locale, whether the hub's TOOLS are on), the hash that
# anchors incremental review, and the rule INDEX. Where the tools are on,
# that is all the review step is handed: it reads rule text and the code
# graph through the hub's tools (get_code_rules, get_code_rule,
# get_repo_ecosystem, find_code_consumers, …) as it reviews. Where they
# are off, the same call carries the full rule text. Nothing is cached
# between runs: every review asks the hub afresh.
- name: Ask the hub how to review this repository
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 || needs.resolve_command.outputs.pr_number }}
# '@flamingo-review full' asks the hub to WITHHOLD that anchor,
# so this pass re-reads the pull request's whole cumulative diff.
# Empty on every other trigger, which keeps them incremental.
REVIEW_FULL: ${{ needs.resolve_command.outputs.full }}
run: /tmp/code-review-fetch-rules.sh
# Stage checkpoints are their OWN credentialed steps. The review step
# holds the shared webhook secret — unavoidably, since the reviewer's
# Claude calls go through the hub's secret-gated proxy rather than
# straight to Anthropic — but NOT the run token, which is the credential
# that can close out a run row. So a checkpoint ping happens at a step
# boundary, not inside the step that reads untrusted PR content.
# Say so ON THE PULL REQUEST before spending minutes in the model. Placed
# AFTER the rules step on purpose: the skip decision is known here, so a run that
# reviews nothing never leaves a placeholder to strand. The comment
# carries the summary marker, so the final "Post to the pull request"
# step UPDATES this exact comment into the verdict rather than adding a
# second one, and it is re-created (not edited) each run so the live
# review sits at the BOTTOM of the timeline instead of scrolling away
# under later discussion. Best-effort: never fails the review.
- name: Say the review is running
if: (github.event_name == 'pull_request' || needs.resolve_command.outputs.pr_number != '') && steps.rules.outputs.skip != 'true'
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || needs.resolve_command.outputs.pr_number }}
WF_RUN_ID: ${{ github.run_id }}
# Feature-DETECTED, not assumed: across a deploy boundary this workflow
# can be handed a post.mjs that predates --running, which would ignore
# the flag and run the full post pass with no findings.json — posting a
# bogus verdict mid-review. Grep first; skip quietly if unsupported.
run: |
if grep -aq -- "argv.includes('--running')" /tmp/code-review-post.mjs; then
node /tmp/code-review-post.mjs --running
else
echo "the hub's post.mjs predates --running — skipping the placeholder"
fi
# An INFRASTRUCTURE skip owes the pull request a word; an opt-out does
# not. Every PR-facing step above and below is gated on
# steps.rules.outputs.skip != 'true', so when the hub's rule endpoint is
# down the pull request got a green completed run and NO comment at all —
# indistinguishable from a reviewer that never triggered, which is the
# exact state the placeholder machinery exists to eliminate.
# Gated on the STABLE skip_kind token, not on 'degraded' (which carries
# the HTTP code and would need copy per code). review_disabled never
# reaches here: silence is the right answer to opting out.
- name: Say the hub could not be reached
if: (github.event_name == 'pull_request' || needs.resolve_command.outputs.pr_number != '') && steps.rules.outputs.skip_kind == 'corpus_unavailable'
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || needs.resolve_command.outputs.pr_number }}
WF_RUN_ID: ${{ github.run_id }}
run: |
if grep -aq -- "argv.includes('--resolve')" /tmp/code-review-post.mjs; then
node /tmp/code-review-post.mjs --resolve corpus_unavailable || true
else
echo "the hub's post.mjs predates --resolve — the pull request gets no notice"
fi
- 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 || needs.resolve_command.outputs.pr_number }}
run: /tmp/code-review-report.sh --progress review
# The graph library's runtime (wasm tree-sitter + grammars + yaml), pinned
# and installed into an isolated tree under RUNNER_TEMP; the reviewer
# reaches it through CODE_GRAPH_DEPS_DIR. continue-on-error: a registry
# outage degrades the review to "no cross-repo facts" (the reviewer
# treats an unset CODE_GRAPH_DEPS_DIR as "no graph"), it never fails it.
# The command is CODE_GRAPH_INSTALL_COMMAND — one spelling for both workflows.
# Gated on the library having been downloaded: without it nothing can
# import this runtime, so installing it would be wasted work and would
# export a CODE_GRAPH_DEPS_DIR no script reads.
- name: Install the code-graph parsers for the deletion gate
if: steps.rules.outputs.skip != 'true' && steps.scripts.outputs.graph_lib == 'true'
continue-on-error: true
run: mkdir -p "$RUNNER_TEMP/code-graph-deps" && cd "$RUNNER_TEMP/code-graph-deps" && printf '%s' '{"name":"code-graph-deps","version":"1.0.0","private":true,"dependencies":{"web-tree-sitter":"0.27.0","@vscode/tree-sitter-wasm":"0.3.1","yaml":"2.9.1"}}' > package.json && printf '%s' '{"name":"code-graph-deps","version":"1.0.0","lockfileVersion":3,"requires":true,"packages":{"":{"name":"code-graph-deps","version":"1.0.0","dependencies":{"web-tree-sitter":"0.27.0","@vscode/tree-sitter-wasm":"0.3.1","yaml":"2.9.1"}},"node_modules/web-tree-sitter":{"version":"0.27.0","resolved":"https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.27.0.tgz","integrity":"sha512-XK08gj6RwTMQatAG7uVRP8MunqotL/XC19vHgkSPKmELgbGPBj4ECvB8haHOUnyj6ls2B8t42UTro14zxGgAHg=="},"node_modules/@vscode/tree-sitter-wasm":{"version":"0.3.1","resolved":"https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.3.1.tgz","integrity":"sha512-RJFoomET6FajjG511fmQxeBQfU6M24a0aFZPqpid+ttIxanWf1VGytBG0UmsGjt07qmIPJS8U31D+aecuCucsQ=="},"node_modules/yaml":{"version":"2.9.1","resolved":"https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz","integrity":"sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="}}}' > package-lock.json && npm ci --ignore-scripts --no-audit --no-fund && echo "CODE_GRAPH_DEPS_DIR=$RUNNER_TEMP/code-graph-deps" >> "$GITHUB_ENV" || { echo "::warning::graph dependencies failed their lockfile-enforced install; continuing without them"; rm -rf "$RUNNER_TEMP/code-graph-deps"; exit 1; }
# Reviews the change against the hub's rules and code graph. With the
# tools on, the reviewer calls the hub for rule text and graph facts as
# it works (its log prints every call under "Tool use"); with them off,
# it works from the text the previous step fetched. Either way the
# deterministic deletion gate asks the hub who consumes what a finding
# would remove.
- name: Review with the rules and code graph from the hub
# 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).
# The reviewer calls Claude THROUGH THE HUB (/api/ci/claude, the
# unified caller), so the step carries the hub's workflow secret and
# the target repo needs no ANTHROPIC_API_KEY of its own.
#
# ANTHROPIC_API_KEY is NOT passed, and must not come back. It was a
# transition shim for the deploy boundary — the scripts come from the
# hub while this file ships in the repo, so a NEW workflow could run an
# OLD script that still called Anthropic directly. That window is shut:
# no shipped code-review script reads the variable — on main as well
# as on this branch, which is what matters for the hub's OWN pull
# requests, whose workflow runs from the PR head against scripts main
# serves. A build gate asserts the two halves stay in agreement, so the key
# cannot drift back into the one step that runs node over untrusted
# pull-request content.
env:
WEBHOOK_SECRET: ${{ secrets.DOC_ORCH_WEBHOOK_SECRET }}
REVIEW_BASE_REF: ${{ github.event.pull_request.base.ref || needs.resolve_command.outputs.base_ref }}
# The RUN STAMP. The reviewer and the miner write .review-run-id from
# this value and the report step discards every artifact not stamped
# with ITS run id — so this step MUST carry it. It did not, for one
# commit in this branch's history: the stamp would have been written
# empty on every CI run, the report step would have deleted the run's
# own findings, and every review, sweep and mine would have reported
# zero findings under a green status while the pull request showed
# them. A build gate now asserts the writer's step has it.
WF_RUN_ID: ${{ github.run_id }}
# Where "Install graph dependencies" put the graph library's runtime;
# empty when that step failed, which the reviewer reads as "no graph".
CODE_GRAPH_DEPS_DIR: ${{ env.CODE_GRAPH_DEPS_DIR }}
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 || needs.resolve_command.outputs.pr_number }}
run: /tmp/code-review-report.sh --progress post
- name: Post to the pull request
id: post
if: (github.event_name == 'pull_request' || needs.resolve_command.outputs.pr_number != '') && steps.rules.outputs.skip != 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEGRADED: ${{ steps.review.outputs.degraded }}
PR_NUMBER: ${{ github.event.pull_request.number || needs.resolve_command.outputs.pr_number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || needs.resolve_command.outputs.head_sha }}
WF_RUN_ID: ${{ github.run_id }}
run: node /tmp/code-review-post.mjs
# TWO steps, one per outcome, because the reason has to be EXACT and a
# status function (cancelled()) is only valid in an if: condition, never
# in an env: block where it would be read as a literal. Reading
# job.status from env instead was close but not guaranteed to say
# 'cancelled' during a concurrency supersede — and getting it wrong means
# telling the author a superseded run CRASHED. Cancellation is the case
# that matters most: it reports no step failure at all, which is the
# orphan source seen in production.
#
# ONE renderer, so the outcome is rendered from ONE value per step: the
# fallback branch reports $OUTCOME while the flag carries its own copy,
# and two hand-kept spellings would eventually name different outcomes in
# the same comment.
- name: Close out the running comment (cancelled)
if: cancelled() && (github.event_name == 'pull_request' || needs.resolve_command.outputs.pr_number != '') && steps.rules.outputs.skip != 'true' && steps.post.outcome != 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || needs.resolve_command.outputs.pr_number }}
WF_RUN_ID: ${{ github.run_id }}
run: |
if grep -aq -- "argv.includes('--resolve')" /tmp/code-review-post.mjs; then
node /tmp/code-review-post.mjs --resolve cancelled || true
else
echo "the hub's post.mjs predates --resolve — leaving it to the run that superseded this one"
fi
- name: Close out the running comment (crashed)
if: failure() && (github.event_name == 'pull_request' || needs.resolve_command.outputs.pr_number != '') && steps.rules.outputs.skip != 'true' && steps.post.outcome != 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number || needs.resolve_command.outputs.pr_number }}
WF_RUN_ID: ${{ github.run_id }}
REPO_FULL: ${{ github.repository }}
OUTCOME: crashed
run: |
if grep -aq -- "argv.includes('--resolve')" /tmp/code-review-post.mjs; then
node /tmp/code-review-post.mjs --resolve crashed || true
else
echo "the hub's post.mjs predates --resolve — posting a standalone notice instead"
BODY=$(printf '%s\n%s\n%s\n\n%s' '<!-- flamingo-code-review -->' '<!-- flamingo-code-review no-verdict -->' '## 🦩 Flamingo Code Review' "**Did not finish on this push** ($OUTCOME). No findings were posted: this is an infrastructure state, not a clean review. See the [workflow run](${GITHUB_SERVER_URL:-https://github.com}/$REPO_FULL/actions/runs/$WF_RUN_ID).")
CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG"
trap 'rm -f "$CURL_CFG"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$GITHUB_TOKEN" > "$CURL_CFG"
curl -s -K "$CURL_CFG" -X POST "${GITHUB_API_URL:-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
fi
# 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 || needs.resolve_command.outputs.head_sha || github.sha }}
WF_RUN_ID: ${{ github.run_id }}
REPO_FULL: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || needs.resolve_command.outputs.pr_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"
# RUN_TOKEN rides jq's env for the same reason the bearer rides a
# curl config file — see curlAuthPreamble.
CURL_CFG=$(mktemp) && chmod 600 "$CURL_CFG"
trap 'rm -f "$CURL_CFG"' EXIT
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
exit 1
fi
/tmp/code-review-report.sh