Skip to content

Add daily OpenAPI PR reviewer agentic workflow#6742

Open
jencarlucci wants to merge 4 commits into
mainfrom
jc/openapi-pr-reviewer-workflow
Open

Add daily OpenAPI PR reviewer agentic workflow#6742
jencarlucci wants to merge 4 commits into
mainfrom
jc/openapi-pr-reviewer-workflow

Conversation

@jencarlucci

@jencarlucci jencarlucci commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What

Adds a daily gh-aw (GitHub Agentic Workflow) that automates the API Platform team's openapi-pr-reviewer process, tracked in github/api-platform#3408.

Every day the workflow:

  1. Finds the latest open "Update OpenAPI 3.0 Descriptions" and "Update OpenAPI 3.1 Descriptions" PRs opened by github-openapi-bot.
  2. Analyzes the diffs locally with git (the GitHub diff API returns 422 / changed_files: 0 on these ~64-file, 250K-line PRs) to detect breaking changes.
  3. If no breaking changes → merges the latest 3.0 + 3.1 PRs into main, closes the older superseded PRs, and posts a summary to #api-platform on Slack.
  4. If a breaking change is found → does not merge, and posts an alert to #api-platform so a human can review.

Files

  • .github/workflows/openapi-pr-reviewer.md — the agentic workflow (source of truth).
  • .github/workflows/openapi-pr-reviewer.lock.yml — compiled workflow (gh aw compile, v0.79.6). This is what Actions runs; regenerate it whenever the .md changes.

Design

The agent runs read-only (contents: read, pull-requests: read). All privileged writes happen in gated safe-output jobs that hold the write tokens — gh-aw's defense against prompt injection:

  • merge-openapi-prs (contents: write, pull-requests: write): merges the two latest PRs into the default branch via the GitHub merge API (gh pr merge --merge) and closes the older PRs. We can't use the built-in merge-pull-request safe output because it always refuses merges to the default branch, so this is a custom job. Merging a PR is the compliant path through the "require a pull request" branch rule, so no protected-branch push or branch-protection bypass is needed. The synchronous merge call can gateway-timeout (502/504) on these ~250K-line diffs, so the job retries and polls the PR's merged state (the merge usually completes server-side despite the timeout).
  • post-to-chatterbox: posts the breaking-change alert to #api-platform (≤200 chars) via chatterbox.

🔒 Security review (required for new secrets)

This workflow introduces the following new secrets / variables. Each has been reviewed:

Name Type Purpose Why it's safe
CHATTERBOX_URL secret Base URL of the chatterbox service used to post notifications to #api-platform Only used inside the two safe-output jobs; the message is POSTed to ${CHATTERBOX_URL}/topics/%23api-platform. Same secret already used by this repo's linter-failure-notifier.yml.
CHATTERBOX_TOKEN secret Basic-auth token for chatterbox (chat:write equivalent) Only used inside the two safe-output jobs; sent only to the chatterbox host as basic-auth (-u "$CHATTERBOX_TOKEN:"). Same secret already used by linter-failure-notifier.yml.
OPENAPI_MERGE_TOKEN secret Merge the two latest PRs and close older superseded PRs Only used inside the merge-openapi-prs job. Needs contents: write + pull-requests: write on this repo. Because the job merges PRs (rather than pushing to the default branch), it does not need any protected-branch bypass. Recommend a GitHub App / service-account token, not a personal PAT.

No third-party GitHub Actions beyond actions/checkout@v5 and actions/github-script@v9 are used. The agent has no network access to write anywhere except through these jobs.

⚠️ Ships in staged (dry-run) mode

The workflow is committed with safe-outputs.staged: true, so every safe-output job previews its action in the run summary instead of actually merging/closing/posting. This lets us validate the daily run end-to-end before it can change anything.

Note: gh-aw does not inject GH_AW_SAFE_OUTPUTS_STAGED into custom safe-output jobs, so the two custom jobs set it explicitly in their env: and gate destructive actions in-script (not via step if:, since a step's own step-level env isn't visible to its if:).

To go live (follow-up, owner action)

  1. Provision the secrets above (CHATTERBOX_URL, CHATTERBOX_TOKEN, OPENAPI_MERGE_TOKEN) and the Copilot engine token (COPILOT_GITHUB_TOKEN / COPILOT_CLI_TOKEN per the org's gh-aw setup). CHATTERBOX_URL / CHATTERBOX_TOKEN already exist in this repo for linter-failure-notifier.yml.
  2. Ensure the OPENAPI_MERGE_TOKEN identity has pull-requests: write + contents: write on this repo and can merge PRs into main. No branch-protection bypass is required, since the job merges the PRs rather than pushing to the default branch. (main currently has no required status checks, so no CodeQL exemption is needed.)
  3. Confirm the chatterbox topic (#api-platform, hardcoded in the safe-output jobs) is correct.
  4. Flip staged mode off: set safe-outputs.staged to false (or remove it) and set GH_AW_SAFE_OUTPUTS_STAGED to "false" (or remove it) in both custom jobs' env: in the .md, then re-run gh aw compile openapi-pr-reviewer.

See github/api-platform#3408 for the full design/tracking notes.

Adds a gh-aw (GitHub Agentic Workflow) that runs the openapi-pr-reviewer logic daily: reviews the latest "Update OpenAPI 3.0/3.1 Descriptions" PRs, merges them and closes older superseded PRs when there are no breaking changes, and notifies #api-platform on Slack. Ships in staged (dry-run) mode until secrets and branch-protection bypass are provisioned. Tracked in github/api-platform#3408.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 10, 2026 18:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new scheduled gh-aw (GitHub Agentic Workflow) to automatically review and (when safe) merge the latest OpenAPI 3.0/3.1 description update PRs from github-openapi-bot, otherwise alerting #api-platform on Slack when breaking changes are detected.

Changes:

  • Introduces a daily openapi-pr-reviewer agentic workflow definition (.md) with two gated safe-output jobs (merge/close + Slack notify; breaking-change Slack alert).
  • Adds the compiled workflow lockfile (.lock.yml) generated by gh aw compile (v0.79.6) for Actions execution.
Show a summary per file
File Description
.github/workflows/openapi-pr-reviewer.md Defines the agentic workflow, including safe-output jobs for merging PRs locally and posting alerts to Slack.
.github/workflows/openapi-pr-reviewer.lock.yml Generated compiled workflow that GitHub Actions will run.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 1/2 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment on lines +227 to +229
# The #api-platform Slack channel ID. Update this to the real channel ID before enabling.
env:
GH_AW_SLACK_CHANNEL_ID: "${{ vars.API_PLATFORM_SLACK_CHANNEL_ID }}"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 4edc3aa. gh-aw doesn't inject GH_AW_SAFE_OUTPUTS_STAGED into custom safe-output jobs (only its own built-in jobs), so it was unset. I now set it explicitly in each custom job's env: and, because a step's own step-level env isn't visible to that same step's if:, gate the destructive actions in-script ($GH_AW_SAFE_OUTPUTS_STAGED for run steps, process.env for github-script) rather than via step if:.

Comment on lines +74 to +76
env:
GH_AW_SLACK_CHANNEL_ID: ${{ vars.API_PLATFORM_SLACK_CHANNEL_ID }}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4edc3aa — same root cause as the .md comment. The compiled custom jobs now carry GH_AW_SAFE_OUTPUTS_STAGED: "true" in their step env and gate merge/close/post in-script, so staged (dry-run) mode actually previews instead of executing.

Comment on lines +158 to +173
- name: Checkout default branch
uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ env.MERGE_TOKEN }}
- name: Merge PRs locally and push
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED != 'true' }}
env:
PR_30: ${{ steps.req.outputs.pr_30 }}
PR_31: ${{ steps.req.outputs.pr_31 }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
DEFAULT_BRANCH="${GITHUB_REF_NAME}"
git checkout "$DEFAULT_BRANCH"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 4edc3aa. The job no longer derives the branch from GITHUB_REF_NAME or pushes directly — it merges each PR via gh pr merge --merge, which targets the PR's base (the default branch) regardless of the ref the workflow was dispatched from. The staged preview now echoes github.event.repository.default_branch.

jencarlucci and others added 3 commits July 10, 2026 12:59
Replace the direct Slack Web API calls (chat.postMessage + SLACK_BOT_TOKEN /
channel ID) with the repo's existing chatterbox convention, matching
linter-failure-notifier.yml: POST to ${CHATTERBOX_URL}/topics/%23api-platform
with basic auth (CHATTERBOX_TOKEN). Renames the post-to-slack-channel safe
output to post-to-chatterbox and drops the SLACK_BOT_TOKEN /
API_PLATFORM_SLACK_CHANNEL_ID secrets/vars. Recompiled the lock with gh aw.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dcc1f083-b349-4398-ae7e-e2b90b1924b7
Replace the local git merge + direct push to the protected default branch
with `gh pr merge` on each of the two latest PRs. Merging a PR is the
compliant path through the "require a pull request" branch rule, so the job
no longer needs a protected-branch push or any ruleset/branch-protection
bypass — OPENAPI_MERGE_TOKEN only needs pull-requests:write + contents:write.

The synchronous merge call can gateway-timeout (502/504) on these ~250K-line
diffs, so merge_pr retries and polls the PR's merged state after each failure
(the merge usually completes server-side despite the timeout). Drops the
actions/checkout-with-token step. Recompiled the lock with gh aw.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dcc1f083-b349-4398-ae7e-e2b90b1924b7
- Staged mode was a no-op: gh-aw does not inject GH_AW_SAFE_OUTPUTS_STAGED
  into custom safe-output jobs, and a step's own step-level env is not visible
  to that same step's `if:`. Set the flag explicitly in each custom job's env
  and gate destructive actions in-script (shell `$GH_AW_SAFE_OUTPUTS_STAGED`
  for run steps, `process.env` for github-script) instead of via step `if:`.
- Merge target: merges now go through `gh pr merge` (targets each PR's base =
  default branch, not GITHUB_REF_NAME), so workflow_dispatch from a non-default
  branch can't merge into the wrong branch. Staged preview echoes
  github.event.repository.default_branch.
- Reference secrets.OPENAPI_MERGE_TOKEN directly for GH_TOKEN (the previous
  `env.MERGE_TOKEN` indirection resolved empty for custom jobs).
- Close-older-PRs now also requires success() so a failed merge won't close PRs.

Recompiled the lock with gh aw.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: dcc1f083-b349-4398-ae7e-e2b90b1924b7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants