Skip to content

Organization-wide workflows #112

Organization-wide workflows

Organization-wide workflows #112

name: "Claude Org-wide Agent"
on:
issue_comment:
types: [created]
permissions:
contents: read
jobs:
check-trigger:
name: "Check trigger phrase and eligibility"
if: github.event.issue.number == 15
runs-on: ubuntu-latest
timeout-minutes: 1
outputs:
triggered: ${{ steps.check.outputs.triggered }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: "Check for trigger phrase"
id: check
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
if echo "$COMMENT_BODY" | grep -qF "@phpstan-bot"; then
echo "triggered=true" >> "$GITHUB_OUTPUT"
else
echo "triggered=false" >> "$GITHUB_OUTPUT"
fi
list-repos:
name: "List public repositories"
needs: check-trigger
if: needs.check-trigger.outputs.triggered == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
repos: ${{ steps.list.outputs.repos }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: "List all public repositories in the organization"
id: list
env:
GH_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }}
run: |
repos=$(gh api --paginate "/orgs/${{ github.repository_owner }}/repos?type=public&per_page=100" \
--jq '[.[].full_name]' | jq -s -c 'add')
echo "repos=$repos" >> "$GITHUB_OUTPUT"
run-on-repo:
name: "Run on ${{ matrix.repo }}"
needs: [check-trigger, list-repos]
if: needs.check-trigger.outputs.triggered == 'true'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
pull-requests: write
id-token: write
strategy:
fail-fast: false
max-parallel: 10
matrix:
repo: ${{ fromJson(needs.list-repos.outputs.repos) }}
exclude:
- repo: phpstan/phpstan-shim
- repo: phpstan/phpstan
- repo: phpstan/phpstan-src
- repo: phpstan/phpstan-phar-composer-source
- repo: phpstan/mutant-killer-infection-runner
- repo: phpstan/phpstan-php-parser
- repo: phpstan/vim-phpstan
- repo: phpstan/.github
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: "Get default branch of target repository"
id: default-branch
env:
GH_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }}
run: |
default_branch=$(gh api "repos/${{ matrix.repo }}" --jq '.default_branch')
echo "branch=$default_branch" >> "$GITHUB_OUTPUT"
- name: "Checkout target repository"
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
repository: ${{ matrix.repo }}
token: ${{ secrets.PHPSTAN_BOT_TOKEN }}
ref: ${{ steps.default-branch.outputs.branch }}
- name: "Extract request from comment"
id: request
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
REQUEST=$(echo "$COMMENT_BODY" | sed 's|@phpstan-bot||g' | sed 's/^[[:space:]]*//')
delimiter="$(openssl rand -hex 16)"
echo "request<<${delimiter}" >> "$GITHUB_OUTPUT"
echo "$REQUEST" >> "$GITHUB_OUTPUT"
echo "${delimiter}" >> "$GITHUB_OUTPUT"
- name: "Run Claude Code on repository"
uses: anthropics/claude-code-action@35a9e0292d36f1186f5d842b14eb575074e8b450 # v1.0.57
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model claude-opus-4-6 --max-turns 50"
bot_name: "phpstan-bot"
bot_id: "79867460"
prompt: |
You are an AI assistant working on the repository ${{ matrix.repo }}.
You are being triggered by an issue comment in the phpstan/.github repository. The comment contains a request that should be applied to this repository.
Here is the request:
${{ steps.request.outputs.request }}
Follow these steps:
1. First, check if there is a CLAUDE.md file in the repository root. If it exists, read and follow its instructions and guidelines.
2. Understand the request carefully. Read any relevant code before making changes. Do not modify code you have not read.
3. Implement the requested changes:
- Keep changes focused and minimal — only make what was requested.
- Do not add unnecessary features, refactoring, or documentation beyond what was asked.
- Be careful not to introduce security vulnerabilities.
- Do not over-engineer the solution.
4. After making changes, commit and create a pull request:
- Stage your changes with git add.
- Write a clear, descriptive commit message that explains why the change was made.
- Push your branch and create a non-draft pull request using gh pr create.
- The PR title should be concise and descriptive.
- The PR body should clearly describe what was changed and why.
- Do not just push a branch — always open a real, non-draft pull request so the changes can be reviewed and merged.
5. After creating the pull request, write the PR URL to the file /tmp/pr-url.txt. The gh pr create command outputs the PR URL — capture it and write it to that file. This is critical for tracking which PRs were opened across all repositories.
Important:
- Never force push or use destructive git commands.
- Never commit files that may contain secrets (.env, credentials, etc.).
- Only make changes that are directly requested or clearly necessary.
- name: "Sanitize repo name"
if: always()
id: repo-name
run: echo "sanitized=$(echo '${{ matrix.repo }}' | tr '/' '-')" >> "$GITHUB_OUTPUT"
- name: "Upload PR URL artifact"
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-url-${{ steps.repo-name.outputs.sanitized }}
path: /tmp/pr-url.txt
if-no-files-found: ignore
post-comment:
name: "Post PR links comment"
needs: [run-on-repo]
if: always() && needs.run-on-repo.result != 'skipped'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: "Download all PR URL artifacts"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: pr-url-*
path: pr-urls
merge-multiple: false
- name: "Post comment with PR links"
env:
GH_TOKEN: ${{ secrets.PHPSTAN_BOT_TOKEN }}
run: |
# Collect all PR URLs from downloaded artifacts
PR_LIST=""
for file in pr-urls/*/pr-url.txt; do
if [ -f "$file" ]; then
while IFS= read -r url; do
if [ -n "$url" ]; then
PR_LIST="${PR_LIST}- ${url}"$'\n'
fi
done < "$file"
fi
done
if [ -n "$PR_LIST" ]; then
BODY=$(printf '### PRs opened by Claude\n\n%s' "$PR_LIST")
gh api -X POST "repos/${{ github.repository }}/issues/15/comments" \
-f body="$BODY"
fi