feat: reusable Claude Code workflow with workflows write permission#77
feat: reusable Claude Code workflow with workflows write permission#77
Conversation
…port Centralizes the Claude Code prompt and config into a reusable workflow (claude-code-reusable.yml) so repo-level claude.yml files are thin callers. Adds github_token input using GH_PAT_WORKFLOWS secret to grant workflows write permission, unblocking Claude from pushing .github/workflows/ changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughIntroduces a new reusable GitHub Actions workflow that consolidates Claude Code automation logic previously split across jobs and steps. The existing workflow is refactored to call this centralized reusable workflow, reducing duplication while maintaining the same trigger conditions and execution paths. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
There was a problem hiding this comment.
Pull request overview
Centralizes the repository’s Claude Code automation by moving the full workflow logic into a reusable GitHub Actions workflow and converting .github/workflows/claude.yml into a thin caller, including support for using a PAT to enable workflow-file updates.
Changes:
- Added a reusable workflow (
claude-code-reusable.yml) containing both interactive and issue-automation Claude jobs. - Simplified
.github/workflows/claude.ymlto call the reusable workflow viaworkflow_callwith inherited secrets. - Introduced passing a PAT (
GH_PAT_WORKFLOWS) to Claude issue automation to allow changes under.github/workflows/.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| .github/workflows/claude.yml | Replaced in-repo job definitions with a reusable workflow caller and delegated configuration. |
| .github/workflows/claude-code-reusable.yml | New reusable workflow containing the full Claude interactive + issue automation configuration, including PAT support. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
| claude-code: | ||
| uses: petry-projects/.github/.github/workflows/claude-code-reusable.yml@main |
There was a problem hiding this comment.
jobs.<job>.uses points at .../claude-code-reusable.yml@main, which is a mutable branch ref. This conflicts with the repo’s action pinning policy (standards/ci-standards.md:425-447) and creates supply-chain / reproducibility risk if main changes unexpectedly. Pin the reusable workflow to a commit SHA (or a version tag that is itself immutably managed) and update via Dependabot or a controlled bump process.
| uses: petry-projects/.github/.github/workflows/claude-code-reusable.yml@main | |
| uses: petry-projects/.github/.github/workflows/claude-code-reusable.yml@<40-character-commit-sha> |
| required: true | ||
| GH_PAT_WORKFLOWS: | ||
| description: "PAT with workflows scope — lets Claude push .github/workflows/ changes" | ||
| required: false |
There was a problem hiding this comment.
GH_PAT_WORKFLOWS is declared as an optional secret (required: false), but the workflow always passes it to anthropics/claude-code-action as github_token. If the secret isn’t set in a given repo/org, this will likely resolve to an empty value and can break the claude-issue job. Either make the secret required, or default github_token to github.token when GH_PAT_WORKFLOWS is not provided.
| required: false | |
| required: true |
| timeout-minutes: 60 | ||
| claude-code: | ||
| uses: petry-projects/.github/.github/workflows/claude-code-reusable.yml@main | ||
| secrets: inherit |
There was a problem hiding this comment.
The reusable workflow is invoked with secrets: inherit, which passes all repository/org secrets to the called workflow even though only CLAUDE_CODE_OAUTH_TOKEN (+ optionally GH_PAT_WORKFLOWS) are needed. To reduce blast radius if this workflow is modified/compromised, prefer explicitly mapping only the required secrets when calling the reusable workflow.
| secrets: inherit | |
| secrets: | |
| CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| GH_PAT_WORKFLOWS: ${{ secrets.GH_PAT_WORKFLOWS }} |
| on: | ||
| workflow_call: | ||
| secrets: |
There was a problem hiding this comment.
This reusable workflow defines multiple jobs but does not reset top-level permissions to {}. The repo standard (standards/ci-standards.md:451-466) requires multi-job workflows to set permissions: {} at the workflow root and then scope per-job, to avoid future jobs inheriting default token permissions unexpectedly.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/claude-code-reusable.yml:
- Around line 75-77: The workflow currently passes an optional secret directly
to the action via the github_token input using ${ secrets.GH_PAT_WORKFLOWS },
which can evaluate to an empty string; either remove the github_token input so
the action uses the default GITHUB_TOKEN (align with the standard template) or
supply an explicit fallback expression that uses the PAT when present and falls
back to the repo token (e.g., adjust the github_token input for the job that
currently sets claude_code_oauth_token and github_token to reference
GH_PAT_WORKFLOWS with a fallback to github.token); update the inputs where
github_token and claude_code_oauth_token are set to implement one of these two
options.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 0df51c1b-ac84-4a63-92aa-741daf9e92e5
📒 Files selected for processing (2)
.github/workflows/claude-code-reusable.yml.github/workflows/claude.yml
| with: | ||
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | ||
| github_token: ${{ secrets.GH_PAT_WORKFLOWS }} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how anthropics/claude-code-action handles github_token input
# Look for default value or fallback behavior in action.yml
ast-grep --pattern $'inputs:
$$$
github_token:
$$$
'
# Also search for github_token usage in the action source
rg -n 'github_token' --type yamlRepository: petry-projects/.github
Length of output: 48
🌐 Web query:
anthropics/claude-code-action github_token input documentation
💡 Result:
The github_token input for anthropics/claude-code-action is documented in:
- Repo
action.yml(source of truth for inputs): defines an optionalgithub_tokeninput (“GitHub token with repo and pull request permissions (optional if using GitHub App)”). [1] - Repo docs (
docs/usage.md): describesgithub_tokenas “GitHub token for Claude to operate with” and notes you should only include this if you’re connecting a custom GitHub app of your own. [2] - Anthropic/Claude Code GitHub Actions docs: lists
github_tokenas an optional parameter (“GitHub token for API access”) and shows examples passing a GitHub App token (e.g., fromactions/create-github-app-token) intogithub_token. [3]
Typical usage (custom GitHub App token):
- uses: anthropics/claude-code-action@v1
with:
github_token: ${{ steps.app-token.outputs.token }}[3]
If you don’t set it: the action can use the default workflow GITHUB_TOKEN / installed Claude GitHub App flow instead. [1][3]
Sources: [1] action.yml in anthropics/claude-code-action; [2] docs/usage.md in anthropics/claude-code-action; [3] Claude Code “GitHub Actions” documentation.
🌐 Web query:
GitHub Actions secrets expression syntax fallback || operator
💡 Result:
In GitHub Actions expressions, || is the logical OR operator, and it’s commonly used as a fallback mechanism because falsy values include "" (empty string) and null (among others). GitHub lists || under expression operators, and notes that "" and null are falsy in conditionals/coercion. [1]
For secrets specifically: if a secret is not set, ${{ secrets.NAME }} evaluates to an empty string (""). [2] That makes || work well for fallbacks.
Examples
Fallback to another secret:
env:
TOKEN: ${{ secrets.PRIMARY_TOKEN || secrets.SECONDARY_TOKEN }}Fallback to a literal default:
env:
REGION: ${{ secrets.AWS_REGION || 'us-east-1' }}Chain multiple fallbacks:
env:
KEY: ${{ secrets.KEY_V3 || secrets.KEY_V2 || secrets.KEY_V1 }}Notes:
- This works because an unset secret becomes
""(falsy), so the next operand is chosen. [2] - GitHub docs also note secrets can’t be directly referenced in
if:conditionals (so use the fallback to set an env var, then use that env var inif:if needed). [2]
Sources: GitHub Actions expressions/operators and falsy values [1]; GitHub Actions secrets behavior when unset + if: limitation [2].
[1] (docs.github.com)
[2] (docs.github.com)
Citations:
- 1: https://docs.github.com/en/actions/reference/evaluate-expressions-in-workflows-and-actions
- 2: https://docs.github.com/actions/security-guides/using-secrets-in-github-actions
🏁 Script executed:
# Check if there's any documentation in the current repo about how this action is configured
# or if there are other workflows using this action with github_token
rg -n 'claude-code-action' -A 5 -B 5 --type yamlRepository: petry-projects/.github
Length of output: 3431
🏁 Script executed:
# Check if GH_PAT_WORKFLOWS is required or truly optional in the current workflow file
fd 'claude-code-reusable.yml' -x cat -n {} | head -30
# Also verify the context around line 77 to understand the full scope
fd 'claude-code-reusable.yml' -x cat -n {} | sed -n '70,80p'Repository: petry-projects/.github
Length of output: 48
🏁 Script executed:
# Search for workflow files in the repo
fd -e yml -e yaml | grep -i workflows | head -20
# Also check git status to see what files are in the repo
git ls-files | grep -i workflowRepository: petry-projects/.github
Length of output: 760
🏁 Script executed:
# Read the claude-code-reusable.yml file to see the context around lines 75-77
cat -n .github/workflows/claude-code-reusable.yml | sed -n '1,95p'Repository: petry-projects/.github
Length of output: 5109
Optional secret GH_PAT_WORKFLOWS passed without fallback—should omit or use explicit fallback.
Line 14 declares GH_PAT_WORKFLOWS as optional (required: false), but line 77 passes it directly to github_token. When the secret is not configured:
${{ secrets.GH_PAT_WORKFLOWS }}evaluates to an empty string, which may be treated differently by the action than omitting the input entirely- The standard template (standards/workflows/claude.yml, line 87-92) omits
github_tokenfor the same job, allowing the action to use the defaultGITHUB_TOKEN
Either omit the input (matching the standard) or provide an explicit fallback:
Option 1: Use fallback to github.token (explicit)
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
- github_token: ${{ secrets.GH_PAT_WORKFLOWS }}
+ github_token: ${{ secrets.GH_PAT_WORKFLOWS || github.token }}Option 2: Omit github_token (aligns with standard template)
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
- github_token: ${{ secrets.GH_PAT_WORKFLOWS }}
label_trigger: "claude"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| with: | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| github_token: ${{ secrets.GH_PAT_WORKFLOWS }} | |
| with: | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| label_trigger: "claude" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/claude-code-reusable.yml around lines 75 - 77, The
workflow currently passes an optional secret directly to the action via the
github_token input using ${ secrets.GH_PAT_WORKFLOWS }, which can evaluate to an
empty string; either remove the github_token input so the action uses the
default GITHUB_TOKEN (align with the standard template) or supply an explicit
fallback expression that uses the PAT when present and falls back to the repo
token (e.g., adjust the github_token input for the job that currently sets
claude_code_oauth_token and github_token to reference GH_PAT_WORKFLOWS with a
fallback to github.token); update the inputs where github_token and
claude_code_oauth_token are set to implement one of these two options.


Summary
claude-code-reusable.yml— single source of truth for the orgclaude.ymlto a thin caller that delegates viauses:+secrets: inheritgithub_token: ${{ secrets.GH_PAT_WORKFLOWS }}to theclaude-issuejob, granting Claude theworkflowsscope needed to push.github/workflows/file changesWhy
All 51 open compliance issues labeled
claudetriggered successfully but ~40% involve missing workflow files (agent-shield.yml, codeql.yml, ci.yml). Claude was blocked from creating PRs for these because the Claude GitHub App token intentionally lacksworkflowswrite. The PAT bypasses this limitation.Centralizing the prompt avoids maintaining identical copies of
claude.ymlacross 7 repos.Test plan
GH_PAT_WORKFLOWSorg secret is set with a PAT that hasworkflowscopeclaude.ymlfiles to the slim callerclaudeand confirm the PR is created successfully🤖 Generated with Claude Code
Summary by CodeRabbit