/ .github / workflows / claude.yml
claude.yml
  1  name: Claude Assistant
  2  
  3  on:
  4    issue_comment:
  5      types: [created]
  6    pull_request_review_comment:
  7      types: [created]
  8    issues:
  9      types: [assigned, labeled, opened]
 10    pull_request_review:
 11      types: [submitted]
 12  
 13  jobs:
 14    # ================================================================
 15    # Job 1: Lightweight triage for ALL new issues (including external users)
 16    # Posts welcome comment + adds triage labels. No Claude code execution.
 17    # ================================================================
 18    issue-triage:
 19      if: |
 20        github.event_name == 'issues' &&
 21        github.event.action == 'opened' &&
 22        !contains(toJSON(github.event.issue.labels), 'claude')
 23      runs-on: ubuntu-latest
 24      permissions:
 25        issues: write
 26        contents: read
 27      steps:
 28        - name: Generate GitHub App Token
 29          id: app-token
 30          uses: actions/create-github-app-token@v1
 31          with:
 32            app-id: ${{ secrets.CLAUDE_APP_ID }}
 33            private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
 34            owner: ${{ github.repository_owner }}
 35  
 36        - name: Triage and acknowledge issue
 37          uses: actions/github-script@v7
 38          with:
 39            github-token: ${{ steps.app-token.outputs.token }}
 40            script: |
 41              const issue = context.payload.issue;
 42              const title = (issue.title || '').toLowerCase();
 43              const body = (issue.body || '').toLowerCase();
 44              const content = title + ' ' + body;
 45              const isOwner = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(
 46                issue.author_association
 47              );
 48  
 49              // --- Label triage ---
 50              const labels = [];
 51  
 52              // Kind labels
 53              if (content.includes('feature request') || content.includes('[feature')) {
 54                labels.push('enhancement');
 55              } else if (content.includes('bug') || content.includes('error') || content.includes('crash') || content.includes('traceback')) {
 56                labels.push('bug');
 57              } else if (content.includes('question') || content.includes('how do i') || content.includes('how to')) {
 58                labels.push('question');
 59              }
 60  
 61              // Area labels
 62              if (content.includes('security') || content.includes('cve') || content.includes('vulnerability')) {
 63                labels.push('security');
 64              }
 65              if (content.includes('performance') || content.includes('slow') || content.includes('memory leak')) {
 66                labels.push('performance');
 67              }
 68              if (content.includes('documentation') || content.includes('docs')) {
 69                labels.push('documentation');
 70              }
 71              if (content.includes('typescript') || content.includes('javascript') || content.includes('npm')) {
 72                labels.push('javascript');
 73              }
 74  
 75              // Apply labels
 76              if (labels.length > 0) {
 77                await github.rest.issues.addLabels({
 78                  issue_number: issue.number,
 79                  owner: context.repo.owner,
 80                  repo: context.repo.repo,
 81                  labels
 82                });
 83              }
 84  
 85              // --- Acknowledgment comment (external users only) ---
 86              if (!isOwner) {
 87                await github.rest.issues.createComment({
 88                  issue_number: issue.number,
 89                  owner: context.repo.owner,
 90                  repo: context.repo.repo,
 91                  body: [
 92                    `👋 Thanks for opening this issue, @${issue.user.login}!`,
 93                    '',
 94                    'A maintainer will review this shortly. In the meantime:',
 95                    '- Make sure you\'ve included steps to reproduce (for bugs)',
 96                    '- Check [existing issues](https://github.com/MervinPraison/PraisonAI/issues) for duplicates',
 97                    '- Review the [documentation](https://docs.praison.ai) for related guides',
 98                    '',
 99                    '_A maintainer can trigger deeper analysis by commenting `@claude` on this issue._'
100                  ].join('\n')
101                });
102              }
103  
104              // --- For owner: add 'claude' label to trigger the code-fix job ---
105              if (isOwner) {
106                await github.rest.issues.addLabels({
107                  issue_number: issue.number,
108                  owner: context.repo.owner,
109                  repo: context.repo.repo,
110                  labels: ['claude']
111                });
112              }
113  
114    # ================================================================
115    # Job 2: Full Claude code-fix (owner/collaborator only)
116    # Triggers on: labeled 'claude', @claude comments, assignments
117    # ================================================================
118    claude-response:
119      # Allow: human users, github-actions[bot] (auto-comments/labels)
120      # Block: dependabot, cursor, renovate, other bots
121      # Skip 'opened' events — triage job handles those above
122      if: |
123        github.event.action != 'opened' &&
124        (github.event.action != 'labeled' || github.event.label.name == 'claude') &&
125        (github.event_name != 'issue_comment' || contains(github.event.comment.body, '@claude')) &&
126        (
127          !contains(github.actor, '[bot]') ||
128          github.actor == 'github-actions[bot]' ||
129          github.actor == 'praisonai-triage-agent[bot]'
130        ) &&
131        github.actor != 'dependabot[bot]' &&
132        github.actor != 'cursor[bot]' &&
133        github.actor != 'renovate[bot]'
134      runs-on: ubuntu-latest
135      permissions:
136        contents: write
137        pull-requests: write
138        issues: write
139        actions: read
140        id-token: write
141      steps:
142        - name: Generate GitHub App Token
143          id: app-token
144          uses: actions/create-github-app-token@v1
145          with:
146            app-id: ${{ secrets.CLAUDE_APP_ID }}
147            private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
148            owner: ${{ github.repository_owner }}
149  
150        - name: Check for Fork PR
151          id: check_fork
152          uses: actions/github-script@v7
153          with:
154            script: |
155              const isPR = context.eventName === 'pull_request' || (context.payload.issue && context.payload.issue.pull_request);
156              if (isPR) {
157                const prNumber = context.payload.pull_request ? context.payload.pull_request.number : context.payload.issue.number;
158                const pr = await github.rest.pulls.get({
159                  owner: context.repo.owner,
160                  repo: context.repo.repo,
161                  pull_number: prNumber
162                });
163                
164                core.setOutput('pr_branch', pr.data.head.ref);
165                
166                if (pr.data.head.repo && pr.data.head.repo.full_name !== context.repo.full_name) {
167                  core.setOutput('is_fork', 'true');
168                  core.setOutput('clone_url', pr.data.head.repo.clone_url);
169                  core.setOutput('branch', pr.data.head.ref);
170                  return;
171                }
172              }
173              core.setOutput('is_fork', 'false');
174  
175        - name: Checkout repository
176          uses: actions/checkout@v4
177          with:
178            persist-credentials: false
179            fetch-depth: 0
180            token: ${{ steps.app-token.outputs.token }}
181  
182        - name: Fetch PR branch and Setup Remote
183          if: github.event.issue.pull_request
184          run: |
185            # Fetch PR head from base repo and put it into local branch
186            git fetch origin pull/${{ github.event.issue.number }}/head:${{ steps.check_fork.outputs.pr_branch }}
187            
188            # If it's a fork, make origin point to local to trick claude-code-action's `git fetch origin <branch>`
189            if [ "${{ steps.check_fork.outputs.is_fork }}" == "true" ]; then
190              git remote set-url origin file://$(pwd)
191            fi
192  
193        - uses: anthropics/claude-code-action@beta
194          env:
195            GH_TOKEN: ${{ steps.app-token.outputs.token }}
196          with:
197            allowed_bots: 'praisonai-triage-agent[bot]'
198            claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
199            github_token: ${{ steps.app-token.outputs.token }}
200            trigger_phrase: "@claude"
201            label_trigger: "claude"
202  
203            direct_prompt: |
204              ${{ steps.check_fork.outputs.is_fork == 'true' && 'CRITICAL: THIS IS A PULL REQUEST FROM A FORK. YOU DO NOT HAVE PUSH PERMISSIONS TO THIS REPOSITORY. ONLY READ FILES, VALIDATE CODE, AND PROVIDE YOUR COMMENTS/FEEDBACK DIRECTLY IN THIS PR VIA COMMENTS. DO NOT ATTEMPT TO PUSH OR CREATE PULL REQUESTS.\n\n' || 'NOTE: The branch is under MervinPraison/PraisonAI (not a fork). You are able to make modifications and push directly to this branch.\n\n' }}
205              You are working on the PraisonAI SDK. Follow AGENTS.md strictly.
206  
207              STEP 0 — SETUP GIT IDENTITY & AUTH (GLOBAL — required for all repos):
208              git config --global user.name "MervinPraison"
209              git config --global user.email "454862+MervinPraison@users.noreply.github.com"
210              gh auth setup-git
211  
212  
213              STEP 1 — READ GUIDELINES:
214              Read AGENTS.md to understand the architecture rules.
215              SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify or touch praisonai-rust or praisonai-ts packages.
216  
217              STEP 2 — ARCHITECTURE VALIDATION & ROUTING (MANDATORY before writing code):
218              Before implementing anything, answer these questions:
219              - CORE vs WRAPPER vs TOOLS vs DOCS vs PLUGINS ROUTING:
220                1. Core SDK (praisonaiagents/): Only core protocols, base classes, decorators. No heavy implementations.
221                2. Wrapper (praisonai/): CLI, heavy implementations, optional dependencies.
222                3. Tools (PraisonAI-Tools): Capabilities Agents actively call during execution (e.g. SurrealDB tool, Slack tool).
223                4. Documentation (PraisonAIDocs): Documentation pages, guides, API references.
224                5. Plugins (PraisonAI-Plugins): Framework lifecycle extensions replacing systemic behavior (e.g. tracing, logging, metrics, hooks, guardrails).
225                For items routed to an EXTERNAL repository (Tools, Docs, Plugins), follow STEP 3-ALT below.
226              - Does it duplicate existing functionality? Check if Agent already supports this via existing params (reflection, planning, tools, hooks, memory).
227              - Does it inherit from Agent properly? New agent types MUST inherit Agent, not wrap it with composition.
228              - Does it add new dependencies? Only optional deps allowed, must be lazy-imported.
229              - Will agent.py grow larger? If the change adds >50 lines to agent.py, find a way to extract instead.
230              - Is there a name collision with existing exports in __init__.py?
231              If ANY of these conceptual checks fail (excluding routing), add a comment to the issue explaining why and close it. Do NOT create a PR.
232  
233              STEP 3 — IMPLEMENT (for changes in THIS repo — PraisonAI):
234              - Create a fix branch: git checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
235              - Implement a minimal, focused fix
236              - Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper
237              - Keep changes small and backward-compatible
238              - Commit: git add -A && git commit -m "fix: <description> (fixes #$ISSUE_NUMBER)"
239              - Push: git push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
240              CRITICAL: After pushing, you MUST immediately run: gh pr create --title "fix: <title>" --body "Fixes #$ISSUE_NUMBER" --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
241              Do NOT stop after pushing. Do NOT say "manually create a PR". Always create the PR automatically.
242  
243              STEP 3-ALT — IMPLEMENT IN EXTERNAL REPO (for PraisonAI-Tools, PraisonAIDocs, PraisonAI-Plugins):
244              When work must happen in a different repository, follow these steps EXACTLY. Do NOT attempt to use `cd`, as directory state is not preserved. Use `git -C` for all commands.
245              a) Clone the repository:
246                 gh repo clone MervinPraison/<REPO_NAME> /tmp/<REPO_NAME>
247              b) Copy GitHub authentication from the main repository so push works seamlessly:
248                 git -C /tmp/<REPO_NAME> config http."https://github.com/".extraheader "$(git config --get http."https://github.com/".extraheader)"
249              c) Create a feature branch (NEVER commit to main):
250                 git -C /tmp/<REPO_NAME> checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
251              d) Make your changes using absolute paths (e.g. edit /tmp/<REPO_NAME>/docs.json)
252              e) Commit:
253                 git -C /tmp/<REPO_NAME> add -A
254                 git -C /tmp/<REPO_NAME> commit -m "feat: <description> (fixes #$ISSUE_NUMBER)"
255              f) Push the branch:
256                 git -C /tmp/<REPO_NAME> push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
257              g) Create PR in the EXTERNAL repo:
258                 gh pr create -R MervinPraison/<REPO_NAME> --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) --title "feat: <title>" --body "Fixes MervinPraison/PraisonAI#$ISSUE_NUMBER
259  
260                 <body>"
261  
262              STEP 4 — TEST:
263              - For SDK changes: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30
264              - For docs changes: verify files exist and are valid markdown/MDX
265              - Ensure no regressions
266  
267              STEP 5 — CREATE PR:
268              - Commit with descriptive message, push, and create PR using `gh pr create`
269              - For external repos, use `gh pr create -R MervinPraison/<REPO_NAME>`
270              CRITICAL: You MUST create the PR automatically using `gh pr create`. Do NOT just provide a link or say "manual push required".
271              NOTE: If you worked on an external repo, the wrapper action will report "No commits" for the main repo at the end. This is expected, do not worry about it.
272            allowed_tools: |
273              Bash(git:*)
274              Bash(python:*)
275              Bash(pip:*)
276              Bash(conda:*)
277              Bash(pytest:*)
278              Bash(gh:*)
279              Bash(python -m pytest:*)
280              Bash(python -m pip:*)
281              Bash(poetry:*)
282              View
283              GlobTool
284              GrepTool
285              BatchTool
286              Edit
287              Replace
288              mcp__github__get_issue
289              mcp__github__get_issue_comments
290              mcp__github__update_issue
291            timeout_minutes: 30