Skip to content

Required OpenCode Review ContextualWisdomLab/fast-mlsirm#1506@773b70f37138bc55b51e33ed0bdc9bb7f2db82a6 #7413

Required OpenCode Review ContextualWisdomLab/fast-mlsirm#1506@773b70f37138bc55b51e33ed0bdc9bb7f2db82a6

Required OpenCode Review ContextualWisdomLab/fast-mlsirm#1506@773b70f37138bc55b51e33ed0bdc9bb7f2db82a6 #7413

name: Required OpenCode Review
run-name: >-
Required OpenCode Review ${{ github.event.pull_request.base.repo.full_name ||
github.repository }}#${{ github.event.pull_request.number || 'event' }}@${{
github.event.pull_request.head.sha || github.sha }}
on:
# This required-workflow entrypoint never checks out or executes pull-request
# content and never binds repository secrets. Privileged review execution is
# isolated in opencode-review-dispatch.yml on repository_dispatch only.
pull_request_target:
# `converted_to_draft` is included so a draft conversion gets an immediate
# exempting run. Every non-closed
# admission path revalidates the live PR/head/state before dispatching,
# exempting, or checking the receipt so out-of-order draft/ready/closed
# events cannot publish stale evidence.
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft, closed]
concurrency:
# Coalesce before runner admission. The live-head job and scheduler still
# reject or replace a delayed stale event after native queue cancellation.
group: >-
required-opencode-review-${{
github.event.pull_request.base.repo.full_name || github.repository }}-${{
github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
id-token: write
jobs:
required-workflow-bootstrap:
name: required-workflow-bootstrap
runs-on: ubuntu-24.04
steps:
- name: Materialize the required review workflow
run: >-
echo "Required OpenCode workflow materialized without checking out or
executing pull-request content."
- name: Reject untrusted fork review resource consumption
env:
PR_ACTION: ${{ github.event.action }}
BASE_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name }}
HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
run: |
set -euo pipefail
if [ "$PR_ACTION" = "closed" ]; then
echo "PR closed; fork-resource-consumption check is not required."
exit 0
fi
if [ -z "$BASE_REPOSITORY" ] || [ -z "$HEAD_REPOSITORY" ] || [ "$HEAD_REPOSITORY" != "$BASE_REPOSITORY" ]; then
echo "::error::Long-running required review is restricted to branches in the base repository. A maintainer must materialize an external contribution on a trusted branch before review."
exit 1
fi
- name: Resolve immutable central policy source
id: trusted_source
env:
JOB_CONTEXT_JSON: ${{ toJSON(job) }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
WORKFLOW_REF: ${{ github.workflow_ref }}
run: |
set -euo pipefail
python3 <<'PY' >>"$GITHUB_OUTPUT"
import json
import os
import re
import sys
try:
job_context = json.loads(os.environ.get("JOB_CONTEXT_JSON") or "{}")
except json.JSONDecodeError as exc:
print(f"::error::Could not parse GitHub workflow context JSON: {exc}", file=sys.stderr)
raise SystemExit(1)
expected_repository = "ContextualWisdomLab/.github"
expected_file = ".github/workflows/opencode-review.yml"
workflow_sha = str(
job_context.get("workflow_sha") or os.environ.get("WORKFLOW_SHA") or ""
).strip()
workflow_ref = str(
job_context.get("workflow_ref") or os.environ.get("WORKFLOW_REF") or ""
).strip()
workflow_ref_head, separator, _ = workflow_ref.partition("@")
if not separator:
print("::error::Required workflow ref is missing its immutable ref separator.", file=sys.stderr)
raise SystemExit(1)
ref_parts = workflow_ref_head.split("/", 2)
if len(ref_parts) < 2 or not ref_parts[0] or not ref_parts[1]:
print("::error::Required workflow ref does not identify a repository.", file=sys.stderr)
raise SystemExit(1)
workflow_repository = "/".join(ref_parts[:2])
workflow_file_path = str(job_context.get("workflow_file_path") or "").strip()
if not workflow_file_path:
prefix = f"{expected_repository}/{expected_file}@"
if workflow_ref.startswith(prefix):
workflow_file_path = expected_file
if workflow_repository != expected_repository:
print(
f"::error::Required workflow repository resolved to {workflow_repository}, expected {expected_repository}.",
file=sys.stderr,
)
raise SystemExit(1)
if not re.fullmatch(r"[0-9a-fA-F]{40}", workflow_sha):
print("::error::Required workflow SHA is missing or malformed.", file=sys.stderr)
raise SystemExit(1)
if workflow_file_path != expected_file:
print("::error::Required workflow file path is missing or unexpected.", file=sys.stderr)
raise SystemExit(1)
expected_ref_prefix = f"{expected_repository}/{expected_file}@"
if not workflow_ref.startswith(expected_ref_prefix):
print("::error::Required workflow ref is missing or inconsistent.", file=sys.stderr)
raise SystemExit(1)
print(f"repository={workflow_repository}")
print(f"sha={workflow_sha}")
print(f"workflow_file_path={workflow_file_path}")
PY
- name: Materialize trusted central policy source
env:
GH_TOKEN: ${{ github.token }}
TRUSTED_SOURCE_REF: ${{ steps.trusted_source.outputs.sha }}
run: |
set -euo pipefail
if [[ ! "$TRUSTED_SOURCE_REF" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::Trusted central policy source ref must resolve to the immutable workflow commit SHA before archive materialization."
exit 1
fi
trusted_archive="${RUNNER_TEMP}/trusted-opencode-policy-source.tar.gz"
trusted_source_dir="${GITHUB_WORKSPACE}/.cwl-required-source"
api_url="${GITHUB_API_URL:-https://api.github.com}"
mkdir -p "$trusted_source_dir"
curl -fsSL \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-o "$trusted_archive" \
"${api_url}/repos/ContextualWisdomLab/.github/tarball/${TRUSTED_SOURCE_REF}"
python3 - "$trusted_archive" "$trusted_source_dir" <<'PY'
import shutil
import sys
import tarfile
from pathlib import Path, PurePosixPath
archive_path = Path(sys.argv[1])
root = Path(sys.argv[2])
try:
if root.is_symlink():
raise ValueError("trusted source directory must not be a symlink")
if root.exists():
shutil.rmtree(root)
root.mkdir(parents=True)
with tarfile.open(archive_path, "r:gz") as archive:
members = archive.getmembers()
top_levels: set[str] = set()
targets: set[str] = set()
directories: list[tuple[tarfile.TarInfo, tuple[str, ...]]] = []
files: list[tuple[tarfile.TarInfo, tuple[str, ...]]] = []
for member in members:
name = member.name
if not name or name.startswith("/") or "\x00" in name or "\\" in name:
raise ValueError(f"unsafe archive member path: {name!r}")
parts = PurePosixPath(name).parts
if not parts or parts[0] in {".", ".."}:
raise ValueError(f"unsafe archive member path: {name!r}")
top_levels.add(parts[0])
relative_parts = parts[1:]
if not relative_parts:
if not member.isdir():
raise ValueError("archive root must be a directory")
continue
if any(part in {"", ".", ".."} for part in relative_parts):
raise ValueError(f"unsafe archive member path: {name!r}")
relative_key = "/".join(relative_parts)
if relative_key in targets:
raise ValueError(f"duplicate archive member path: {relative_key}")
targets.add(relative_key)
if member.isdir():
directories.append((member, relative_parts))
elif member.isfile():
files.append((member, relative_parts))
else:
raise ValueError(f"unsupported archive member type: {name!r}")
if len(top_levels) != 1:
raise ValueError("archive must contain exactly one top-level directory")
for _member, relative_parts in sorted(
directories, key=lambda item: len(item[1])
):
(root / Path(*relative_parts)).mkdir(parents=True, exist_ok=True)
for member, relative_parts in files:
destination = root / Path(*relative_parts)
destination.parent.mkdir(parents=True, exist_ok=True)
source = archive.extractfile(member)
if source is None:
raise ValueError(f"archive member is not readable: {member.name!r}")
with source, destination.open("xb") as output:
shutil.copyfileobj(source, output)
except (OSError, tarfile.TarError, ValueError) as exc:
raise SystemExit(f"trusted source archive failed closed: {exc}") from exc
PY
- name: Verify immutable central policy source
env:
EXPECTED_FILE: ${{ steps.trusted_source.outputs.workflow_file_path }}
run: |
set -euo pipefail
trusted_source_dir="$GITHUB_WORKSPACE/.cwl-required-source"
if [ ! -f "$trusted_source_dir/$EXPECTED_FILE" ] || [ -L "$trusted_source_dir/$EXPECTED_FILE" ]; then
printf '::error::Required workflow source file is missing or symlinked: %s.\n' \
"$EXPECTED_FILE"
exit 1
fi
if [ ! -f "$trusted_source_dir/scripts/ci/pingora_edge_policy.py" ] || [ -L "$trusted_source_dir/scripts/ci/pingora_edge_policy.py" ]; then
echo "::error::Trusted Pingora edge policy helper is missing or symlinked."
exit 1
fi
- name: Enforce Cloudflare Pingora edge policy
env:
GITHUB_TOKEN: ${{ github.token }}
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number || 0 }}
PULL_REQUEST_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
EVENT_ACTION: ${{ github.event.action || 'unknown' }}
run: |
set -euo pipefail
python3 .cwl-required-source/scripts/ci/pingora_edge_policy.py \
--repository "$TARGET_REPOSITORY" \
--pull-request "$PULL_REQUEST_NUMBER" \
--head-sha "$PULL_REQUEST_HEAD_SHA" \
--event-action "$EVENT_ACTION" \
--api-url "https://api.github.com"
admit-current-head:
name: admit-current-head
needs: [required-workflow-bootstrap]
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
admitted: ${{ steps.live_head.outputs.admitted }}
permissions:
contents: read
pull-requests: read
steps:
- name: Admit only the exact live OpenCode head
id: live_head
env:
GH_TOKEN: ${{ github.token }}
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || '' }}
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || '' }}
EXPECTED_ACTION: ${{ github.event.action || '' }}
run: |
set -euo pipefail
echo "admitted=false" >>"$GITHUB_OUTPUT"
if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::OpenCode admission rejected malformed pull request metadata."
exit 1
fi
live_pr="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")"
live_head="$(jq -r '.head.sha // empty' <<<"$live_pr")"
live_state="$(jq -r '.state // empty' <<<"$live_pr")"
expected_state=open
[ "$EXPECTED_ACTION" = "closed" ] && expected_state=closed
if [ "${live_head,,}" != "${EXPECTED_HEAD_SHA,,}" ] || [ "$live_state" != "$expected_state" ]; then
echo "::notice::OpenCode admission retired a stale event before review queue entry."
exit 0
fi
echo "admitted=true" >>"$GITHUB_OUTPUT"
echo "Exact live OpenCode head admitted for ${TARGET_REPOSITORY}#${PR_NUMBER}."
coverage-source-tree:
name: coverage-source-tree
needs: [required-workflow-bootstrap, admit-current-head]
if: needs.admit-current-head.outputs.admitted == 'true'
runs-on: ubuntu-24.04
steps:
- run: >-
echo "PR-head source and coverage execution are delegated to the
authenticated default-branch OpenCode review dispatch."
coverage-evidence:
name: coverage-evidence
# Deliberately NOT `needs: [coverage-source-tree]`. Neither job declares
# `outputs:`, so that edge only ordered two single-`echo` context holders --
# and a job is not created until its `needs:` complete, so under a saturated
# queue each link waits out the whole queue again. Measured on
# naruon#1528 (run 33581213805): coverage-source-tree waited 9h40m to run for
# 4s, then coverage-evidence waited a further 13h01m to run for 5s, holding
# the actual review behind ~22h41m of pure queueing. Depending on
# `admit-current-head` directly lets the two run in parallel. The `if:` below
# restates the admission gate this job previously inherited transitively
# through coverage-source-tree, so an unadmitted head still skips it.
needs: [required-workflow-bootstrap, admit-current-head]
if: needs.admit-current-head.outputs.admitted == 'true'
runs-on: ubuntu-24.04
steps:
- run: >-
echo "This required-workflow job preserves the stable branch-protection
context without executing pull-request content."
opencode-review-target:
name: opencode-review
# `coverage-evidence` is deliberately absent here. This job never reads it
# at runtime -- the only consumer of that context is
# `opencode-review-dispatch.yml`, which resolves it through
# `scripts/ci/opencode_coverage_identity.py` against the check-runs API on
# its own schedule, so it does not care when this job ran relative to it.
# The edge was pure ordering, and ordering is expensive: a job is not
# created until its `needs:` finish, so this link cost a further 12h13m of
# queue wait on naruon#1528 (run 33581213805). Admission is still enforced
# directly by this job's own `if:` below, not inherited through that edge.
needs: [admit-current-head]
if: needs.admit-current-head.outputs.admitted == 'true'
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read
id-token: write
steps:
- name: Request current-head OpenCode review execution
if: github.event.action != 'closed'
env:
GH_TOKEN: ${{ github.token }}
OIDC_AUDIENCE: opencode-github-action
OPENCODE_API_BASE_URL: https://api.opencode.ai
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_DRAFT: ${{ github.event.pull_request.draft }}
BASE_BRANCH: ${{ github.event.pull_request.base.ref }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
run: |
set -euo pipefail
live_pr="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")"
live_head="$(printf '%s' "$live_pr" | jq -r '.head.sha // empty')"
live_draft="$(printf '%s' "$live_pr" | jq -r 'if (.draft | type) == "boolean" then (.draft | tostring) else empty end')"
live_state="$(printf '%s' "$live_pr" | jq -r 'if (.state | type) == "string" then .state else empty end')"
if [ -z "$live_head" ] || [ -z "$live_draft" ] || [ -z "$live_state" ]; then
echo "::error::Could not validate live pull request state before review dispatch."
exit 1
fi
if [ "$live_state" != "open" ] && [ "$live_state" != "closed" ]; then
echo "::error::Could not validate live pull request state before review dispatch."
exit 1
fi
if [ "$live_state" = "closed" ]; then
echo "PR is closed on the live exact head; a current-head OpenCode review is not requested."
exit 0
fi
if [ "$live_draft" = "true" ]; then
echo "PR is still a draft on the live exact head; a current-head OpenCode review is not requested until it is marked ready for review."
exit 0
fi
if [ "${live_head,,}" != "${HEAD_SHA,,}" ]; then
echo "Pull request head moved on the live open, ready-for-review PR; a fresh dispatch will fire for the current head."
exit 0
fi
if [ "$PR_DRAFT" = "true" ]; then
echo "Event draft snapshot is stale; continuing current-head OpenCode review dispatch for the live ready PR."
fi
effective_pr_draft="$live_draft"
helper="$(mktemp)"
trap 'rm -f "$helper"' EXIT
gh api "repos/ContextualWisdomLab/.github/contents/scripts/ci/opencode_review_receipt_gate.py?ref=${WORKFLOW_SHA}" \
--jq .content | base64 --decode >"$helper"
receipt_state="$(python3 - "$helper" "$TARGET_REPOSITORY" "$PR_NUMBER" "$HEAD_SHA" "$effective_pr_draft" <<'PY'
import importlib.machinery
import importlib.util
import sys
helper_path, repository, number, head_sha, draft = sys.argv[1:]
loader = importlib.machinery.SourceFileLoader(
"trusted_opencode_receipt_gate", helper_path
)
spec = importlib.util.spec_from_loader(loader.name, loader)
if spec is None or spec.loader is None:
raise RuntimeError("trusted OpenCode receipt helper could not be loaded")
gate = importlib.util.module_from_spec(spec)
spec.loader.exec_module(gate)
reviews = gate.fetch_reviews(repository, int(number))
receipt, _reason = gate.evaluate_receipts(
reviews, head_sha, is_draft=draft.lower() == "true"
)
print("present" if receipt is not None else "missing")
PY
)"
if [ "$receipt_state" = "present" ]; then
echo "Current-head substantive OpenCode verdict already exists; scheduler wake skipped."
exit 0
fi
if [ "$receipt_state" != "missing" ]; then
echo "::error::Trusted OpenCode receipt helper returned an invalid state."
exit 1
fi
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
echo "::error::OpenCode review dispatch requires GitHub OIDC."
exit 1
fi
separator='&'
[[ "$ACTIONS_ID_TOKEN_REQUEST_URL" == *\?* ]] || separator='?'
oidc_token="$(curl -fsS -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" "${ACTIONS_ID_TOKEN_REQUEST_URL}${separator}audience=${OIDC_AUDIENCE}" | jq -r '.value // empty')"
if [ -z "$oidc_token" ]; then
echo "::error::OpenCode review dispatch could not obtain its OIDC token."
exit 1
fi
app_token="$(curl -fsS -X POST -H "Authorization: Bearer ${oidc_token}" "${OPENCODE_API_BASE_URL}/exchange_github_app_token" | jq -r '.token // empty')"
if [ -z "$app_token" ]; then
echo "::error::OpenCode review dispatch could not obtain its repository-scoped app token."
exit 1
fi
echo "::add-mask::$app_token"
jq -cn \
--arg target_repository "$TARGET_REPOSITORY" \
--arg pr_number "$PR_NUMBER" \
--arg pr_base_ref "$BASE_BRANCH" \
--arg pr_base_sha "$BASE_SHA" \
--arg pr_head_ref "$HEAD_REF" \
--arg pr_head_sha "$HEAD_SHA" \
--arg required_run_id "$GITHUB_RUN_ID" \
'{event_type:"opencode-review",client_payload:{target_repository:$target_repository,pr_number:$pr_number,pr_base_ref:$pr_base_ref,pr_base_sha:$pr_base_sha,pr_head_ref:$pr_head_ref,pr_head_sha:$pr_head_sha,required_run_id:$required_run_id}}' |
GH_TOKEN="$app_token" gh api -X POST repos/ContextualWisdomLab/.github/dispatches --input -
- name: Fail closed without a current-head OpenCode verdict
env:
GH_TOKEN: ${{ github.token }}
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_ACTION: ${{ github.event.action }}
PR_DRAFT: ${{ github.event.pull_request.draft }}
run: |
set -euo pipefail
if [ "$PR_ACTION" = "closed" ]; then
echo "PR closed; a current-head OpenCode verdict is not required."
exit 0
fi
if [ -z "${PR_NUMBER:-}" ] || [ -z "${HEAD_SHA:-}" ]; then
echo "::error::Missing PR number or head SHA; cannot verify a current-head OpenCode verdict."
exit 1
fi
live_pr="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")"
live_head="$(printf '%s' "$live_pr" | jq -r '.head.sha // empty')"
live_draft="$(printf '%s' "$live_pr" | jq -r 'if (.draft | type) == "boolean" then (.draft | tostring) else empty end')"
live_state="$(printf '%s' "$live_pr" | jq -r 'if (.state | type) == "string" then .state else empty end')"
if [ -z "$live_head" ] || [ -z "$live_draft" ] || [ -z "$live_state" ]; then
echo "::error::Could not validate live pull request state before verdict admission."
exit 1
fi
if [ "$live_state" != "open" ] && [ "$live_state" != "closed" ]; then
echo "::error::Could not validate live pull request state before verdict admission."
exit 1
fi
if [ "$live_state" = "closed" ]; then
echo "PR is closed on the live exact head; a current-head OpenCode verdict is not required."
exit 0
fi
if [ "$live_draft" = "true" ]; then
echo "PR is still a draft on the live exact head; a current-head OpenCode verdict is not required until it is marked ready for review."
exit 0
fi
if [ "${live_head,,}" != "${HEAD_SHA,,}" ]; then
echo "Pull request head moved on the live open, ready-for-review PR; a fresh run will check the current head."
exit 0
fi
if [ "$PR_DRAFT" = "true" ]; then
echo "Event draft snapshot is stale; checking the verdict for the live ready PR."
fi
reviews="$(gh api --paginate "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}/reviews?per_page=100")"
verdict="$(printf '%s\n' "$reviews" | jq -r -s --arg sha "$HEAD_SHA" '
(add // [])
| [
.[]
| select(
(.user.login // "" | ascii_downcase) as $user
| $user == "opencode-agent" or $user == "opencode-agent[bot]"
)
| select((.commit_id // "" | ascii_downcase) == ($sha | ascii_downcase))
| select(.state == "APPROVED" or .state == "CHANGES_REQUESTED")
]
| (last // {}) as $review
| ($review.body // "" | ascii_downcase) as $body
| if $review.state == "CHANGES_REQUESTED" then
"CHANGES_REQUESTED"
elif $review.state == "APPROVED"
and ($body | contains("deterministic current-head evidence") | not)
and ($body | contains("deterministic fallback approval") | not)
and ($body | contains("model-unavailable evidence fallback") | not)
and ($body | contains("did not emit a usable current-head control block") | not)
and ($body | contains("scope: `unsupported`") | not)
and ($body | contains("model-pool outcome: `unknown`") | not)
then
"APPROVED"
else
empty
end
')"
if [ -z "$verdict" ]; then
echo "::error::No APPROVED or CHANGES_REQUESTED from opencode-agent on the current head. The dispatch workflow will rerun this failed job after publishing an authenticated exact-head verdict."
exit 1
fi
echo "Current-head OpenCode verdict: ${verdict}."
cancel-superseded-opencode-review-runs:
# This job -- not the bootstrap concurrency group above -- is the primary
# mechanism that actively cancels a same-PR run for an outdated head. The
# bootstrap group is now `cancel-in-progress: false` (see its own comment):
# nothing is ever preempted there, by design, to structurally close the
# #1568 stale-cancels-fresh race regardless of arrival order. This job
# achieves precise, safe "cancel only outdated runs of the same PR"
# instead: it re-verifies the live PR head immediately before selecting
# candidates AND immediately before every individual cancellation call, so
# a cleanup run that is itself delayed/stale cannot cancel a
# still-authoritative run, and it only ever targets runs whose recorded
# head no longer matches the live one. The target job also revalidates the
# live PR before dispatch and verdict admission.
if: github.event_name == 'pull_request_target' && github.event.action == 'synchronize'
runs-on: ubuntu-24.04
permissions:
actions: write
contents: read
pull-requests: read
env:
GH_TOKEN: ${{ github.token }}
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
TARGET_PR_NUMBER: ${{ github.event.pull_request.number }}
TARGET_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
CURRENT_RUN_ID: ${{ github.run_id }}
steps:
- name: Cancel queued and running OpenCode review runs for a superseded pull request head
shell: bash
run: |
set -euo pipefail
live_head_matches() {
local live_head
if ! live_head="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${TARGET_PR_NUMBER}" --jq '.head.sha' 2>/tmp/opencode-cleanup-gh-error)"; then
echo "::warning::OpenCode review cleanup could not verify the live pull request head; leaving runs unchanged."
sed 's/^/ /' /tmp/opencode-cleanup-gh-error >&2 || true
return 1
fi
[ "${live_head,,}" = "${TARGET_PR_HEAD_SHA,,}" ]
}
cancel_runs() {
local status="$1"
if ! live_head_matches; then
echo "::notice::OpenCode review cleanup target changed before run selection; leaving runs unchanged."
return 0
fi
local runs_url="repos/${TARGET_REPOSITORY}/actions/runs?status=${status}&per_page=100"
local runs_json
if ! runs_json="$(gh api --paginate "$runs_url" 2>/tmp/opencode-cleanup-gh-error)"; then
echo "::warning::OpenCode review cleanup could not inspect ${TARGET_REPOSITORY}; leaving runs unchanged."
sed 's/^/ /' /tmp/opencode-cleanup-gh-error >&2 || true
return 0
fi
local run_ids
if ! run_ids="$(jq -r --arg pr "$TARGET_PR_NUMBER" --arg head_sha "$TARGET_PR_HEAD_SHA" \
--arg repo "$TARGET_REPOSITORY" --arg current "$CURRENT_RUN_ID" '
.workflow_runs[]
| select((.id | tostring) != $current)
| select(.name == "Required OpenCode Review")
| select(.event == "pull_request_target")
| ((.display_title // "") | startswith("Required OpenCode Review " + $repo + "#" + $pr + "@")) as $title_matches
| ((.pull_requests // []) | any((.number | tostring) == $pr)) as $metadata_matches
| select($title_matches or $metadata_matches)
| ((.display_title // "") | endswith("@" + $head_sha)) as $title_is_current
| ((.pull_requests // []) | any(
((.number | tostring) == $pr)
and ((.head.sha // "") | ascii_downcase) == ($head_sha | ascii_downcase)
)) as $metadata_is_current
| select(($title_is_current or $metadata_is_current) | not)
| .id
' <<<"$runs_json")"; then
echo "::warning::OpenCode review cleanup received invalid run data for ${TARGET_REPOSITORY}; leaving runs unchanged."
return 0
fi
while IFS= read -r run_id; do
[ -n "$run_id" ] || continue
if ! live_head_matches; then
echo "::notice::OpenCode review cleanup target changed before cancellation; leaving runs unchanged."
return 0
fi
if gh api --method POST "repos/${TARGET_REPOSITORY}/actions/runs/${run_id}/cancel" >/dev/null 2>/tmp/opencode-cleanup-cancel-error ||
gh api --method POST "repos/${TARGET_REPOSITORY}/actions/runs/${run_id}/force-cancel" >/dev/null 2>>/tmp/opencode-cleanup-cancel-error; then
echo "Cancelled superseded Required OpenCode Review run ${run_id} in ${TARGET_REPOSITORY} for PR #${TARGET_PR_NUMBER}."
else
echo "::warning::OpenCode review cleanup could not cancel run ${run_id} in ${TARGET_REPOSITORY}; it may have finished or the credential lacks Actions write access."
sed 's/^/ /' /tmp/opencode-cleanup-cancel-error >&2 || true
fi
done <<<"$run_ids"
}
for active_status in queued in_progress requested waiting pending; do
cancel_runs "$active_status"
done
echo "Superseded OpenCode review run cleanup completed."