Skip to content

Poutine & Zizmor

Poutine & Zizmor #109

name: "Claude Org-wide Agent"
on:
issue_comment:
types: [created]
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: "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: "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
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: "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@v4
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@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
claude_args: "--model claude-opus-4-6"
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@v4
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: "Download all PR URL artifacts"
uses: actions/download-artifact@v4
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