feat(pr-review): switch to App-token identity (#250)#252
Conversation
Switch `pr-review/action.yml` from `github.token` to a resolved GitHub App token for all `claude-code-action` invocations. Review comments now post under the App bot identity (e.g. `my-app[bot]`) for consistency with `tag-claude`, `lint-failure`, `ci-failure`, and `apply-fix`. Changes: - `pr-review/action.yml`: add `app_id` / `app_private_key` inputs; insert `Generate App token` (actions/create-github-app-token@1b10c78 # v3.1.1) and `Resolve review token` steps before `claude-code-action`; replace `github_token: ${{ github.token }}` with `steps.token.outputs.value`. - Bot-comment selectors updated at three sites (quality gate, synthesis, shadow gate fallback) plus the incomplete-review warning step — all four change `.user.login == "github-actions[bot]"` / `.author.login == ...` to `endswith("[bot]")` so they match any bot identity, not just `github-actions[bot]`. - `.github/workflows/claude-pr-review.yml`: add `app_id` and `app_private_key` to `workflow_call` secrets block; wire both through to the composite-action step via `secrets.app_id || secrets.APP_ID` and `secrets.app_private_key || secrets.APP_PRIVATE_KEY` for compatibility with both dogfood and consumer naming conventions. - `CLAUDE.md`: reverse the `github.token` carve-out in the token-selection convention; add `pr-review` to the Required secrets table. - `README.md`: update Quick Start PR Review example, migration notes, and prerequisite text to reflect that `APP_ID` + `APP_PRIVATE_KEY` are now required for `pr-review`.
…mments and log strings
Selector logic was already updated to endswith("[bot]") in the previous commit;
this is a follow-up to make explanatory comments and echo log lines reflect the
broader matcher. No logic change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…250 review) Code review surfaced a regression: the authoritative quality-gate selector (line 299) and the shadow-gate fallback selector (line 480) used `sort_by(.updated_at) | last` without a timestamp filter. Under the previous literal `github-actions[bot]` matcher this was safe — only the review job's runner posted as that user. Under the new `endswith("[bot]")` matcher introduced by this PR, any other bot's more-recent comment (Dependabot, Renovate, CodeQL, etc.) could win the sort and the gate would read the wrong body — silent skip-fail of exactly the kind #250 was scoped to avoid. Mirror the synthesis step's timestamp gate (line 370) at both authoritative and shadow sites: filter to comments updated at or after $REVIEW_START_TIME before sorting. Also explicitly forward REVIEW_START_TIME into each step's env: block to make the dependency visible. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Claude finished @cbeaulieu-gt's task in 2m 59s —— View job Claude PR Review — CompleteReviewed PR #252: feat(pr-review): switch to App-token identity (#250) This PR reverses the Findings🟢 Medium — Input validation mismatch creates confusing failure modepr-review/action.yml:9-13, .github/workflows/claude-pr-review.yml:15-18 The composite action declares
The runtime error message is clear, but this inconsistency is confusing: # pr-review/action.yml
app_id:
description: 'GitHub App ID for generating a short-lived token (required for App-identity comments)'
required: false # ← Should this be true?Recommendation: Consider marking these inputs as Impact: Low — the error is caught at runtime with a clear message, but the mismatch creates unnecessary confusion during integration. 🟢 Medium — Timestamp comparison might pick up foreign bot in edge casepr-review/action.yml:305, 376, 489 All three bot-comment selectors use map(select((.user.login | endswith("[bot]")) and .updated_at >= "$REVIEW_START_TIME"))Using Recommendation: Change the timestamp comparison from -map(select((.user.login | endswith("[bot]")) and .updated_at >= "$REVIEW_START_TIME"))
+map(select((.user.login | endswith("[bot]")) and .updated_at > "$REVIEW_START_TIME"))Impact: Very low probability in practice (requires exact timestamp collision to the second in ISO 8601), but the fix is trivial and eliminates the edge case entirely. Nit — Error message assumes missing inputs rather than token generation failurepr-review/action.yml:222-223 The "Resolve review token" step's error message assumes the inputs are missing: echo "::error::No authentication token provided. Set app_id + app_private_key inputs. See README for GitHub App setup instructions."However, the step also runs when Recommendation: Differentiate between missing inputs and token generation failure: if [ -n "${{ inputs.app_id }}" ] && [ -n "${{ inputs.app_private_key }}" ]; then
echo "::error::GitHub App token generation failed. Check APP_ID and APP_PRIVATE_KEY validity."
else
echo "::error::No authentication token provided. Set app_id + app_private_key inputs. See README for GitHub App setup instructions."
fi
exit 1This provides clearer diagnostics for troubleshooting. Impact: Developer experience — clearer error messages reduce time to resolution when integration fails. Nit — Selector synchronization relies on manual disciplinepr-review/action.yml:305, 376, 489 (and comments at lines 294, 370, 479) Three separate code locations use nearly-identical jq selectors for bot comments:
The comments explicitly note these must stay synchronized (~line 294: "Mirrors the synthesis step at ~line 370 and the incomplete-review check at ~line 589"). If a future edit updates one selector but not the others, the gates could diverge and produce inconsistent results. Recommendation: Extract the common jq filter expression into a shell variable or sourced function (similar to how # In a new file lib/bot-comment-selector.sh:
BOT_COMMENT_JQ_FILTER='map(select((.user.login | endswith("[bot]")) and .updated_at > $review_start)) | sort_by(.updated_at) | last'
# In action.yml:
source "${{ github.action_path }}/lib/bot-comment-selector.sh"
BODY=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" \
--jq "$BOT_COMMENT_JQ_FILTER | .body // \"\"" \
--arg review_start "$REVIEW_START_TIME")This ensures all three callsites use the exact same logic. Impact: Maintainability — prevents future drift between selectors that must agree. Strengths✅ Consistent identity across workflows — App-token usage aligns pr-review with tag-claude, lint-failure, ci-failure, and apply-fix. This eliminates the confusing mixed-identity state where most actions posted as ✅ Defensive selector hardening — Adding ✅ SHA-pinned action dependency — ✅ Clear breaking-change documentation — README.md and CLAUDE.md both updated to reflect the new requirement. Migration steps are explicit, and the "Required secrets" table correctly lists pr-review under ✅ Dogfooding validation — This PR's own Code QualityThe implementation is clean and follows established patterns. Variable naming is clear ( SecurityNo security regressions identified. The switch to App tokens is a lateral move (both One minor note: there's no validation that the PerformanceNo performance impact. The API calls remain the same; only the filtering logic changes. The Test CoverageThis repository has no automated test suite (per CLAUDE.md: "There are no other build steps, tests, or package managers"). The test plan in the PR description is manual but comprehensive:
The lack of automated tests means the selector synchronization issue (Nit finding above) relies entirely on code review discipline to catch drift. DocumentationDocumentation is thorough and accurately reflects the changes:
The migration guidance could be slightly more explicit about the failure mode (i.e., "If you don't update, your workflow will fail at the 'Resolve review token' step with an explicit error message"), but the current documentation is sufficient. Verdict: APPROVE |
|
v2.6.0 release withdrawn. This PR was shipped as part of v2.6.0, but the release was rolled back: Reason: the App-token requirement introduced here is breaking for any consumer pinned to an older Tracking the rollout-process problem under #256. The code change here is correct on its own; the failure was in the release/distribution path. A successor release will ship once the consumer-impact gate is in place. 🤖 Generated by Claude Code on behalf of @cbeaulieu-gt |
…ber assoc check Without `secrets: inherit`, the v2.6.1 reusable workflows fail with "No authentication token provided" because they require `app_id` and `app_private_key` (added in glitchwerks/github-actions#252). The auth fallback then misreads cbeaulieu-gt as CONTRIBUTOR (their glitchwerks org membership is private), causing a misleading auth-denial. `authorized_users: cbeaulieu-gt` short-circuits the association check per glitchwerks/github-actions#253. Refs #22
…ber assoc check Without `secrets: inherit`, the v2.6.1 reusable workflows fail with "No authentication token provided" because they require `app_id` and `app_private_key` (added in glitchwerks/github-actions#252). The auth fallback then misreads cbeaulieu-gt as CONTRIBUTOR (their glitchwerks org membership is private), causing a misleading auth-denial. `authorized_users: cbeaulieu-gt` short-circuits the association check per glitchwerks/github-actions#253. Refs #325
* chore(deps): bump glitchwerks/github-actions pins to v2.6.1 (#22) v2.6.1 ships a fix for a pr-review quality-gate false-positive (glitchwerks/github-actions#257). Bump the three reusable-workflow pins from their stale versions: - ci-failure.yaml: v2.3.0 → v2.6.1 - claude-pr-review.yml: v2.5.1 → v2.6.1 - claude-tag-respond.yml: v2.3.0 → v2.6.1 Closes #22 * fix(workflows): pass App secrets via inherit + bypass private-org-member assoc check Without `secrets: inherit`, the v2.6.1 reusable workflows fail with "No authentication token provided" because they require `app_id` and `app_private_key` (added in glitchwerks/github-actions#252). The auth fallback then misreads cbeaulieu-gt as CONTRIBUTOR (their glitchwerks org membership is private), causing a misleading auth-denial. `authorized_users: cbeaulieu-gt` short-circuits the association check per glitchwerks/github-actions#253. Refs #22
* chore(deps): bump glitchwerks/github-actions pins to v2.6.1 (#325) v2.6.1 ships a fix for a pr-review quality-gate false-positive (glitchwerks/github-actions#257). Bump the four reusable-workflow pins from their stale versions: - claude-lint-fix.yml: v2.3.0 → v2.6.1 - ci-failure.yaml: v2.3.0 → v2.6.1 - claude-pr-review.yml: v2.5.1 → v2.6.1 - claude-tag-respond.yml: v2.3.0 → v2.6.1 Closes #325 * fix(workflows): pass App secrets via inherit + bypass private-org-member assoc check Without `secrets: inherit`, the v2.6.1 reusable workflows fail with "No authentication token provided" because they require `app_id` and `app_private_key` (added in glitchwerks/github-actions#252). The auth fallback then misreads cbeaulieu-gt as CONTRIBUTOR (their glitchwerks org membership is private), causing a misleading auth-denial. `authorized_users: cbeaulieu-gt` short-circuits the association check per glitchwerks/github-actions#253. Refs #325
Closes #250
Summary
Reverses the deliberate
github.tokencarve-out documented in #114 for thepr-reviewaction. Under a single-operator personal-repo constraint, the cost of the carve-out (review comments posting asgithub-actions[bot]while every other Claude-powered workflow posts as the App's[bot]identity) outweighs the benefit (avoiding consumer onboarding regression, which doesn't bind here).Changes
pr-review/action.ymlapp_idandapp_private_keycomposite-action inputs.actions/create-github-app-token@1b10c78… # v3.1.1(SHA-pinned, matchingapply-fix/lint-failure/tag-claudepattern) and aResolve review tokenstep that emits a clear error if both inputs are missing.claude-code-action@v1step now consumes${{ steps.token.outputs.value }}forgithub_token(was${{ github.token }}).github-actions[bot]match toendswith("[bot]"):gh pr view --json comments—author.loginnotuser.login)\$REVIEW_START_TIME, mirroring the synthesis step. Without this, a foreign bot (Dependabot, Renovate, CodeQL) updating its comment after Claude's review finishes could silently win the sort under the widenedendswith("[bot]")matcher..github/workflows/claude-pr-review.yml— addsapp_id/app_private_keyto theworkflow_callsecrets:block (required: true), wires through to the composite step withsecrets.app_id || secrets.APP_IDfallback.CLAUDE.md—## Key conventions§ Token selection updated to reflect that all workflows now use the App token; carve-out reversed under feat(pr-review): switch to App-token identity (revisit #114 carve-out) #250. Required-secrets table listspr-reviewunderAPP_ID/APP_PRIVATE_KEY.README.md— Quick Start example, prerequisites, and migration breaking-changes section updated.Test plan
actionlintpasses (verified locally).claude-pr-reviewjob runs under the new App identity and posts a sticky comment thatendswith("[bot]").v2against an external consumer (siege-web or similar) — review run posts under App identity, no consumer regression.REVIEW_START_TIMEguard makes the selector resilient if any are added later.Notes
glitchwerks/github-actions/.github/workflows/claude-pr-review.yml@v2: callers must now passapp_idandapp_private_keysecrets. Migration guidance is in the updated README.gh pr view --json commentswhich exposesauthor.loginrather than the REST API'suser.login— both code paths updated.🤖 Generated by Claude Code on behalf of @cbeaulieu-gt