fix(ci): repost Copilot reviews as @claude comments#99
Conversation
Copilot PR reviews were silently failing claude-code-action@v1 because
its `checkHumanActor` calls `octokit.users.getByUsername('Copilot')`,
which 404s — Copilot is a virtual actor with no user record. No input
bypasses that lookup (see upstream #1018 / #1030 / #1144, all open).
Add a `repost-copilot-review` job that mints the Navigaite workflow
App token, stitches the Copilot review body + inline comments into a
single `@claude` issue comment, and posts it. The resulting
`issue_comment` event fires a fresh Claude run where
`github.actor == 'navigaite-workflow-app[bot]'` — a bot user that
resolves — so `checkHumanActor` takes its `allowed_bots: *` branch.
Also relax the `issue_comment` author-association gate (reusable
workflow + caller template) to admit the reposted comment from our
own app, which has `author_association: NONE` by construction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the shared Claude Code reusable workflow to handle Copilot PR review events that currently fail inside claude-code-action@v1 due to GET /users/Copilot returning 404. It does so by proxying Copilot reviews into a normal issue_comment trigger path that the existing pipeline can process safely.
Changes:
- Add a
repost-copilot-reviewjob to fetch a Copilot review + inline comments and post a single synthesized@claude …PR comment. - Skip running the main
claudejob directly on Copilotpull_request_reviewevents to avoid theusers.getByUsername('Copilot')404. - Relax
issue_commentauthor gating (in both reusable + caller/template) to trust comments authored bynavigaite-workflow-app[bot].
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| .github/workflows/claude-code.yaml | Adds the Copilot-review repost job, skips direct Claude execution for Copilot reviews, and admits navigaite-workflow-app[bot] through the author gate. |
| .github/workflows/claude-code-fix.yaml | Updates the caller workflow if: gate to allow synthesized @claude comments from navigaite-workflow-app[bot]. |
| .github/config/templates/claude-code-fix.yaml | Mirrors the caller gating change in the bootstrap template used by consumer repos. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| contains(github.event.review.user.login, 'copilot') | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| permissions: | ||
| pull-requests: write # post the @claude issue comment | ||
| steps: | ||
| - name: 🔍 Require app secrets | ||
| shell: bash | ||
| env: | ||
| APP_ID: ${{ secrets.WORKFLOW_APP_ID }} | ||
| APP_PRIVATE_KEY: ${{ secrets.WORKFLOW_APP_PRIVATE_KEY }} | ||
| run: | | ||
| if [[ -z "$APP_ID" || -z "$APP_PRIVATE_KEY" ]]; then | ||
| echo "::error::WORKFLOW_APP_ID/WORKFLOW_APP_PRIVATE_KEY required to repost Copilot reviews." | ||
| exit 1 | ||
| fi | ||
|
|
There was a problem hiding this comment.
The workflow_call docs describe WORKFLOW_APP_ID / WORKFLOW_APP_PRIVATE_KEY as optional (with fallback behavior elsewhere in this workflow), but repost-copilot-review hard-fails when they are unset. That will make Copilot review events fail the check in repos that haven’t configured these secrets. Consider gating the job on the secrets being present (so it skips cleanly) or implementing a safe fallback path (e.g., using GITHUB_TOKEN and explicitly allowing the fallback actor in the comment-author gate).
| contains(github.event.review.user.login, 'copilot') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| pull-requests: write # post the @claude issue comment | |
| steps: | |
| - name: 🔍 Require app secrets | |
| shell: bash | |
| env: | |
| APP_ID: ${{ secrets.WORKFLOW_APP_ID }} | |
| APP_PRIVATE_KEY: ${{ secrets.WORKFLOW_APP_PRIVATE_KEY }} | |
| run: | | |
| if [[ -z "$APP_ID" || -z "$APP_PRIVATE_KEY" ]]; then | |
| echo "::error::WORKFLOW_APP_ID/WORKFLOW_APP_PRIVATE_KEY required to repost Copilot reviews." | |
| exit 1 | |
| fi | |
| contains(github.event.review.user.login, 'copilot') && | |
| secrets.WORKFLOW_APP_ID != '' && | |
| secrets.WORKFLOW_APP_PRIVATE_KEY != '' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| pull-requests: write # post the @claude issue comment | |
| steps: |
| --jq '[.[] | "- **`" + .path + (if .line then ":" + (.line|tostring) else "" end) + "`** — " + .body] | join("\n\n")' \ | ||
| || echo "")" |
There was a problem hiding this comment.
The inline-comment fetch swallows any gh api error via || echo "", which can hide real failures (e.g., auth/permission issues or API errors) and still post a synthesized @claude comment missing inline context. Prefer letting the step fail on unexpected errors, or only handling the specific “no inline comments” case explicitly (empty array) while surfacing other failures.
| --jq '[.[] | "- **`" + .path + (if .line then ":" + (.line|tostring) else "" end) + "`** — " + .body] | join("\n\n")' \ | |
| || echo "")" | |
| --jq '[.[] | "- **`" + .path + (if .line then ":" + (.line|tostring) else "" end) + "`** — " + .body] | join("\n\n")')" |
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| permissions: | ||
| pull-requests: write # post the @claude issue comment |
There was a problem hiding this comment.
repost-copilot-review posts an issue comment (/issues/:number/comments), but the job only grants pull-requests: write to GITHUB_TOKEN. If this job ever falls back to GITHUB_TOKEN (or if create-github-app-token is replaced), it will lack the issues: write permission needed to create issue comments. Consider switching this to issues: write (or issues: write + pull-requests: read if you later need PR metadata) and updating the inline comment accordingly.
| pull-requests: write # post the @claude issue comment | |
| issues: write # post the synthesized @claude issue comment on the PR |
🤖 I have created a release *beep* *boop* --- ## [2.6.5](v2.6.4...v2.6.5) (2026-04-18) ### 🐛 Bug Fixes * **ci:** repost Copilot reviews as [@claude](https://github.com/claude) comments ([#99](#99)) ([bf6d42e](bf6d42e)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: navigaite-workflow-app[bot] <133966083+navigaite-workflow-app[bot]@users.noreply.github.com>
Summary
Copilot PR reviews have been silently failing claude-code-action@v1 — its
checkHumanActorstep callsoctokit.users.getByUsername('Copilot'), which 404s becauseCopilotis a virtual actor with no user record. No action input bypasses that lookup (upstream PRs #1018, #1030, #1144 are all still open).Observed on navigaite/edilio#94 run 24551381827:
Fix
Proxy Copilot reviews into an
issue_commentevent that Claude's existing pipeline can handle:repost-copilot-reviewjob in the reusableclaude-code.yaml. Fires only whenpull_request_review.user.type == 'Bot'and login containscopilot. Mints the Navigaite workflow App token, fetches the review's inline comments viaGET /pulls/:n/reviews/:id/comments, and posts a single stitched@claude …comment on the PR.claudejob on Copilot reviews — it would hit the 404. The spawnedissue_commentre-enters the job withgithub.actor == 'navigaite-workflow-app[bot]', a real bot user that resolves, socheckHumanActor'sallowed_bots: *branch takes over.issue_commentauthor-association gate (both the reusable workflow step and the caller template'sif:) to admit comments fromnavigaite-workflow-app[bot]. Reposted comments haveauthor_association: NONEby construction — trust is anchored in the fact that only our installed app can mint its own token.Test plan
repost-copilot-reviewjob succeeds, (2) an@claude …comment appears on the PR, (3) a follow-upissue_commentrun of theclaudejob runs to completion without theusers/Copilot404.@claudecomments still trigger Claude normally (unchanged path).🤖 Generated with Claude Code