fix: propagate AddResult failures and log embedding errors (#2821) #28877
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 Assistant | |
| on: | |
| issue_comment: | |
| types: [created] | |
| pull_request_review_comment: | |
| types: [created] | |
| issues: | |
| types: [assigned, labeled, opened] | |
| pull_request_review: | |
| types: [submitted] | |
| jobs: | |
| # ================================================================ | |
| # Job 1: Lightweight triage for ALL new issues (including external users) | |
| # Posts welcome comment + adds triage labels. No Claude code execution. | |
| # ================================================================ | |
| issue-triage: | |
| if: | | |
| github.event_name == 'issues' && | |
| github.event.action == 'opened' && | |
| !contains(toJSON(github.event.issue.labels), 'claude') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| contents: read | |
| steps: | |
| - name: Generate GitHub App Token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.CLAUDE_APP_ID }} | |
| private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| - name: Triage and acknowledge issue | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token }} | |
| script: | | |
| const issue = context.payload.issue; | |
| const title = (issue.title || '').toLowerCase(); | |
| const body = (issue.body || '').toLowerCase(); | |
| const content = title + ' ' + body; | |
| const isOwner = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes( | |
| issue.author_association | |
| ); | |
| // --- Label triage --- | |
| const labels = []; | |
| // Kind labels | |
| if (content.includes('feature request') || content.includes('[feature')) { | |
| labels.push('enhancement'); | |
| } else if (content.includes('bug') || content.includes('error') || content.includes('crash') || content.includes('traceback')) { | |
| labels.push('bug'); | |
| } else if (content.includes('question') || content.includes('how do i') || content.includes('how to')) { | |
| labels.push('question'); | |
| } | |
| // Area labels | |
| if (content.includes('security') || content.includes('cve') || content.includes('vulnerability')) { | |
| labels.push('security'); | |
| } | |
| if (content.includes('performance') || content.includes('slow') || content.includes('memory leak')) { | |
| labels.push('performance'); | |
| } | |
| if (content.includes('documentation') || content.includes('docs')) { | |
| labels.push('documentation'); | |
| } | |
| if (content.includes('typescript') || content.includes('javascript') || content.includes('npm')) { | |
| labels.push('javascript'); | |
| } | |
| // Apply labels | |
| if (labels.length > 0) { | |
| await github.rest.issues.addLabels({ | |
| issue_number: issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels | |
| }); | |
| } | |
| // --- Acknowledgment comment (external users only) --- | |
| if (!isOwner) { | |
| await github.rest.issues.createComment({ | |
| issue_number: issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: [ | |
| `π Thanks for opening this issue, @${issue.user.login}!`, | |
| '', | |
| 'A maintainer will review this shortly. In the meantime:', | |
| '- Make sure you\'ve included steps to reproduce (for bugs)', | |
| '- Check [existing issues](https://github.com/MervinPraison/PraisonAI/issues) for duplicates', | |
| '- Review the [documentation](https://docs.praison.ai) for related guides', | |
| '', | |
| '_A maintainer can trigger deeper analysis by commenting `@claude` on this issue._', | |
| '', | |
| '**Routing:** Agent-callable tools β [PraisonAI-Tools](https://github.com/MervinPraison/PraisonAI-Tools); lifecycle plugins (tracing/hooks/guardrails) β [PraisonAI-Plugins](https://github.com/MervinPraison/PraisonAI-Plugins).' | |
| ].join('\n') | |
| }); | |
| } | |
| // --- For owner: add 'claude' label to trigger the code-fix job --- | |
| if (isOwner) { | |
| await github.rest.issues.addLabels({ | |
| issue_number: issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels: ['claude'] | |
| }); | |
| } | |
| # ================================================================ | |
| # Job 2: Full Claude code-fix (owner/collaborator only) | |
| # Triggers on: labeled 'claude', @claude comments, assignments | |
| # ================================================================ | |
| claude-response: | |
| # Allow: human users, github-actions[bot] (auto-comments/labels) | |
| # Block: dependabot, cursor, renovate, other bots | |
| # Skip 'opened' events β triage job handles those above | |
| if: | | |
| github.event.action != 'opened' && | |
| (github.event.action != 'labeled' || github.event.label.name == 'claude') && | |
| (github.event_name != 'issue_comment' || contains(github.event.comment.body, '@claude')) && | |
| (github.event_name != 'pull_request_review' || contains(github.event.review.body, '@claude')) && | |
| (github.event_name != 'pull_request_review_comment' || contains(github.event.comment.body, '@claude')) && | |
| ( | |
| !contains(github.actor, '[bot]') || | |
| github.actor == 'github-actions[bot]' || | |
| github.actor == 'praisonai-triage-agent[bot]' | |
| ) && | |
| github.actor != 'dependabot[bot]' && | |
| github.actor != 'cursor[bot]' && | |
| github.actor != 'renovate[bot]' | |
| concurrency: | |
| group: claude-response-${{ github.repository }}-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }} | |
| cancel-in-progress: false | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| actions: read | |
| id-token: write | |
| steps: | |
| - name: Generate GitHub App Token | |
| id: app-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.CLAUDE_APP_ID }} | |
| private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| - name: Check for Fork PR | |
| id: check_fork | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ steps.app-token.outputs.token }} | |
| script: | | |
| const isPR = context.eventName === 'pull_request' | |
| || context.eventName === 'pull_request_review' | |
| || context.eventName === 'pull_request_review_comment' | |
| || (context.payload.issue && context.payload.issue.pull_request); | |
| if (!isPR) { | |
| core.setOutput('is_pr', 'false'); | |
| core.setOutput('is_fork', 'false'); | |
| core.setOutput('can_push_fork', 'false'); | |
| return; | |
| } | |
| const prNumber = context.payload.pull_request | |
| ? context.payload.pull_request.number | |
| : context.payload.issue.number; | |
| const pr = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber | |
| }); | |
| let mergeState = ''; | |
| try { | |
| const gql = await github.graphql( | |
| `query($owner: String!, $repo: String!, $number: Int!) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $number) { mergeStateStatus } | |
| } | |
| }`, | |
| { owner: context.repo.owner, repo: context.repo.repo, number: prNumber } | |
| ); | |
| mergeState = (gql.repository.pullRequest.mergeStateStatus || '').toUpperCase(); | |
| } catch (e) { | |
| core.warning(`GraphQL mergeStateStatus failed: ${e.message}`); | |
| } | |
| if (!mergeState) { | |
| mergeState = (pr.data.mergeable_state || pr.data.merge_state_status || '').toUpperCase(); | |
| } | |
| core.setOutput('is_pr', 'true'); | |
| core.setOutput('pr_number', String(prNumber)); | |
| core.setOutput('pr_branch', pr.data.head.ref); | |
| core.setOutput('head_repo', pr.data.head.repo.full_name); | |
| core.setOutput('merge_state', mergeState); | |
| const baseRepo = `${context.repo.owner}/${context.repo.repo}`; | |
| const maintainerCanModify = pr.data.maintainer_can_modify === true; | |
| core.setOutput('maintainer_can_modify', String(maintainerCanModify)); | |
| if (pr.data.head.repo && pr.data.head.repo.full_name !== baseRepo) { | |
| core.setOutput('is_fork', 'true'); | |
| core.setOutput('can_push_fork', String(maintainerCanModify)); | |
| core.setOutput('fork_repo', pr.data.head.repo.full_name); | |
| core.setOutput('clone_url', pr.data.head.repo.clone_url); | |
| core.setOutput('branch', pr.data.head.ref); | |
| return; | |
| } | |
| core.setOutput('is_fork', 'false'); | |
| core.setOutput('can_push_fork', 'false'); | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 0 | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Fetch PR branch and Setup Remote | |
| if: steps.check_fork.outputs.is_pr == 'true' | |
| env: | |
| PR_BRANCH: ${{ steps.check_fork.outputs.pr_branch }} | |
| PR_NUMBER: ${{ steps.check_fork.outputs.pr_number }} | |
| IS_FORK: ${{ steps.check_fork.outputs.is_fork }} | |
| CAN_PUSH_FORK: ${{ steps.check_fork.outputs.can_push_fork }} | |
| FORK_REPO: ${{ steps.check_fork.outputs.fork_repo }} | |
| GH_TOKEN: ${{ secrets.GH_TOKEN }} | |
| run: | | |
| git fetch origin "pull/${PR_NUMBER}/head:${PR_BRANCH}" | |
| git checkout "${PR_BRANCH}" | |
| git fetch origin main | |
| if [ "${IS_FORK}" = "true" ] && [ "${CAN_PUSH_FORK}" = "true" ]; then | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${FORK_REPO}.git" | |
| elif [ "${IS_FORK}" = "true" ]; then | |
| # Block push: trick claude-code-action's `git fetch origin <branch>` | |
| git remote set-url origin "file://$(pwd)" | |
| fi | |
| - uses: anthropics/claude-code-action@beta | |
| env: | |
| GH_TOKEN: ${{ steps.check_fork.outputs.can_push_fork == 'true' && secrets.GH_TOKEN || steps.app-token.outputs.token }} | |
| with: | |
| allowed_bots: 'praisonai-triage-agent[bot]' | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| github_token: ${{ steps.app-token.outputs.token }} | |
| model: claude-opus-4-8 | |
| trigger_phrase: "@claude" | |
| label_trigger: "claude" | |
| direct_prompt: | | |
| ${{ steps.check_fork.outputs.is_fork == 'true' && steps.check_fork.outputs.can_push_fork != 'true' && 'CRITICAL: THIS IS A PULL REQUEST FROM A FORK WITHOUT MAINTAINER EDITS. YOU DO NOT HAVE PUSH PERMISSIONS. ONLY READ FILES, VALIDATE CODE, AND PROVIDE COMMENTS/FEEDBACK ON THIS PR. DO NOT ATTEMPT TO PUSH OR CREATE PULL REQUESTS.\n\n' || (steps.check_fork.outputs.can_push_fork == 'true' && format('NOTE: Fork PR with maintainer edits allowed. Push directly to fork {0} branch {1}.\n\n', steps.check_fork.outputs.fork_repo, steps.check_fork.outputs.pr_branch) || (steps.check_fork.outputs.is_pr == 'true' && steps.check_fork.outputs.is_fork != 'true' && 'NOTE: The branch is under MervinPraison/PraisonAI (not a fork). You are able to make modifications and push directly to this branch.\n\n' || (steps.check_fork.outputs.is_pr != 'true' && 'NOTE: This is an issue (not a PR). Create a new fix branch per STEP 3 below.\n\n' || ''))) }} | |
| ${{ steps.check_fork.outputs.is_pr == 'true' && format('CONTEXT: PR #{0} on branch {1}. head.repo={2}, is_fork={3}, can_push_fork={4}, merge_state={5}. NEVER treat cursor/*, claude/* branch names or bot authors as forks β only is_fork=true means fork.\n\n', steps.check_fork.outputs.pr_number, steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.head_repo, steps.check_fork.outputs.is_fork, steps.check_fork.outputs.can_push_fork, steps.check_fork.outputs.merge_state) || '' }} | |
| You are working on the PraisonAI SDK. Follow AGENTS.md strictly. | |
| STEP 0 β SETUP GIT IDENTITY & AUTH (GLOBAL β required for all repos): | |
| git config --global user.name "MervinPraison" | |
| git config --global user.email "454862+MervinPraison@users.noreply.github.com" | |
| gh auth setup-git | |
| STEP 1 β READ GUIDELINES: | |
| Read AGENTS.md to understand the architecture rules. | |
| SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify or touch praisonai-rust or praisonai-ts packages. | |
| STEP 2 β ARCHITECTURE VALIDATION & ROUTING (MANDATORY before writing code): | |
| Before implementing anything, answer these questions: | |
| - CORE vs WRAPPER vs TOOLS vs DOCS vs PLUGINS ROUTING: | |
| 1. Core SDK (praisonaiagents/): Only core protocols, base classes, decorators. No heavy implementations. | |
| 2. Wrapper (praisonai/): CLI, heavy implementations, optional dependencies. | |
| 3. Tools β MervinPraison/PraisonAI-Tools: agent-callable integrations at runtime (Composio, Slack, API/DB connectors). Do NOT implement in praisonaiagents/ β use STEP 3-ALT. | |
| 4. Documentation (PraisonAIDocs): Documentation pages, guides, API references. | |
| 5. Plugins β MervinPraison/PraisonAI-Plugins: framework lifecycle extensions (tracing, logging, metrics, hooks, guardrails). Not agent tools β use STEP 3-ALT. | |
| 6. Sandbox backends (optional/niche, e.g. Capsule) β PraisonAI-Plugins via `praisonai.sandbox` entry point; core keeps protocol/config/manager only. | |
| For items routed to an EXTERNAL repository (Tools, Docs, Plugins), follow STEP 3-ALT below. | |
| - Does it duplicate existing functionality? Check if Agent already supports this via existing params (reflection, planning, tools, hooks, memory). | |
| - Do not bloat the Agent class with additional params β only if absolutely required; we already support many params. | |
| - Does it inherit from Agent properly? New agent types MUST inherit Agent, not wrap it with composition. | |
| - Does it add new dependencies? Only optional deps allowed, must be lazy-imported. | |
| - Will agent.py grow larger? If the change adds >50 lines to agent.py, find a way to extract instead. PRs with >100 lines added to agent.py require manual review (merge gate will not auto-merge). | |
| - Is there a name collision with existing exports in __init__.py? | |
| 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. | |
| ${{ steps.check_fork.outputs.is_pr == 'true' && (steps.check_fork.outputs.is_fork != 'true' || steps.check_fork.outputs.can_push_fork == 'true') && steps.check_fork.outputs.merge_state == 'DIRTY' && format('STEP 3 β RESOLVE MERGE CONFLICTS ON PR (branch: {0}, PR #{1}{2}):\n- DO NOT create a new branch or PR\n- git fetch origin main && git rebase origin/main\n- Resolve conflicts: keep this PR intent, merge in main newer logic\n- git add -A && git rebase --continue (repeat until done)\n- git push --force-with-lease origin {0}\n- Run targeted pytest on changed paths\n- Comment files resolved and what you skipped\n', steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.pr_number, steps.check_fork.outputs.can_push_fork == 'true' && format(', fork: {0}', steps.check_fork.outputs.fork_repo) || '') || '' }} | |
| ${{ steps.check_fork.outputs.is_pr == 'true' && (steps.check_fork.outputs.is_fork != 'true' || steps.check_fork.outputs.can_push_fork == 'true') && steps.check_fork.outputs.merge_state != 'DIRTY' && format('STEP 3 β IMPLEMENT ON EXISTING PR (branch: {0}, PR #{1}{2}):\n- DO NOT create a new branch or PR\n- git checkout {0}\n- Implement minimal, focused fixes from reviewer feedback\n- Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper\n- Keep changes small and backward-compatible\n- Commit: git add -A && git commit -m "fix: <description>"\n- Push: git push origin {0}\n- Comment a summary of exact files modified and what you skipped\n', steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.pr_number, steps.check_fork.outputs.can_push_fork == 'true' && format(', fork: {0}', steps.check_fork.outputs.fork_repo) || '') || '' }} | |
| ${{ steps.check_fork.outputs.is_pr != 'true' && 'STEP 3 β IMPLEMENT (for changes in THIS repo β PraisonAI):\n- Create a fix branch: git checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)\n- Implement a minimal, focused fix\n- Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper\n- Keep changes small and backward-compatible\n- Commit: git add -A && git commit -m "fix: <description> (fixes #$ISSUE_NUMBER)"\n- Push: git push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)\nCRITICAL: 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)\nDo NOT stop after pushing. Do NOT say "manually create a PR". Always create the PR automatically.\n' || '' }} | |
| STEP 3-ALT β IMPLEMENT IN EXTERNAL REPO (for PraisonAI-Tools, PraisonAIDocs, PraisonAI-Plugins): | |
| 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. | |
| a) Clone the repository: | |
| gh repo clone MervinPraison/<REPO_NAME> /tmp/<REPO_NAME> | |
| b) Copy GitHub authentication from the main repository so push works seamlessly: | |
| git -C /tmp/<REPO_NAME> config http."https://github.com/".extraheader "$(git config --get http."https://github.com/".extraheader)" | |
| c) Create a feature branch (NEVER commit to main): | |
| git -C /tmp/<REPO_NAME> checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) | |
| d) Make your changes using absolute paths (e.g. edit /tmp/<REPO_NAME>/docs.json) | |
| e) Commit: | |
| git -C /tmp/<REPO_NAME> add -A | |
| git -C /tmp/<REPO_NAME> commit -m "feat: <description> (fixes #$ISSUE_NUMBER)" | |
| f) Push the branch: | |
| git -C /tmp/<REPO_NAME> push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) | |
| g) Create PR in the EXTERNAL repo: | |
| 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 | |
| <body>" | |
| STEP 4 β TEST: | |
| - For SDK changes: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30 | |
| - For docs changes: verify files exist and are valid markdown/MDX | |
| - Ensure no regressions | |
| ${{ steps.check_fork.outputs.is_pr == 'true' && 'STEP 5 β SKIP: Already on an open PR. Do NOT run gh pr create.\n' || 'STEP 5 β CREATE PR:\n- Commit with descriptive message, push, and create PR using `gh pr create`\n- For external repos, use `gh pr create -R MervinPraison/<REPO_NAME>`\nCRITICAL: You MUST create the PR automatically using `gh pr create`. Do NOT just provide a link or say "manual push required".\n' }} | |
| 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. | |
| allowed_tools: | | |
| Bash(git:*) | |
| Bash(python:*) | |
| Bash(pip:*) | |
| Bash(conda:*) | |
| Bash(pytest:*) | |
| Bash(gh:*) | |
| Bash(python -m pytest:*) | |
| Bash(python -m pip:*) | |
| Bash(poetry:*) | |
| View | |
| GlobTool | |
| GrepTool | |
| BatchTool | |
| Edit | |
| Replace | |
| mcp__github__get_issue | |
| mcp__github__get_issue_comments | |
| mcp__github__update_issue | |
| timeout_minutes: 30 | |
| - name: Kick review chain for bot-created PR | |
| if: | | |
| steps.check_fork.outputs.is_pr != 'true' && | |
| (github.event_name == 'issues' || github.event_name == 'issue_comment') && | |
| github.event.issue != null | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const path = require('path'); | |
| const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js')); | |
| const issueNumber = context.payload.issue.number; | |
| await chain.kickReviewChainForIssue( | |
| github, context.repo.owner, context.repo.repo, issueNumber, core | |
| ); | |
| - name: Sync pipeline status labels for PR | |
| if: steps.check_fork.outputs.is_pr == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const path = require('path'); | |
| const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js')); | |
| const prNumber = parseInt('${{ steps.check_fork.outputs.pr_number }}', 10); | |
| await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core); | |
| await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, prNumber, core); | |
| - name: Dispatch merge gate when PR pipeline ready | |
| if: steps.check_fork.outputs.is_pr == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const path = require('path'); | |
| const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js')); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const prNumber = parseInt('${{ steps.check_fork.outputs.pr_number }}', 10); | |
| const check = await mergeGate.evaluatePipelineQuiescent( | |
| github, owner, repo, prNumber, core, { | |
| skipGlobalClaudeRunCheck: true, | |
| skipRecentClaudeCooldown: true, | |
| } | |
| ); | |
| if (!check.ready) { | |
| core.info(`PR #${prNumber} not ready for merge gate: ${check.reasons.join(', ')}`); | |
| return; | |
| } | |
| await github.rest.repos.createDispatchEvent({ | |
| owner, | |
| repo, | |
| event_type: 'claude-merge-gate', | |
| client_payload: { pr_number: prNumber }, | |
| }); | |
| core.info(`Dispatched merge gate for PR #${prNumber}`); |