Poutine & Zizmor #109
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |