Skip to content

Add automation pipeline (merge gate, CI, PyPI release)#5

Merged
MervinPraison merged 3 commits into
mainfrom
infra/automation-pipeline
Jul 8, 2026
Merged

Add automation pipeline (merge gate, CI, PyPI release)#5
MervinPraison merged 3 commits into
mainfrom
infra/automation-pipeline

Conversation

@MervinPraison

@MervinPraison MervinPraison commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Port PraisonAIUI automation pipeline: merge gate, pipeline status sync, CI failure Claude, nightly release gate
  • Add repo-specific gate-config.js scoped to plugin lifecycle (src/praisonai_plugins/, praisonai.plugins, praisonai.sandbox)
  • Add unified CI workflow (ruff + pytest 3.10–3.12) and pypi-release.yml for praisonai-plugins
  • Customise auto-pr-comment.yml FINAL @claude scope for Plugins vs Tools vs SDK layering
  • Add sync-secrets-from-praisonai.yml hub workflow trigger

Test plan

  • All .github/scripts/*-selftest.js pass locally
  • CI green on this PR
  • After merge: run Sync secrets from PraisonAI workflow once
  • Verify pipeline/merge-ready label flow on a test PR

Summary by CodeRabbit

  • New Features

    • Added automated PR review, merge-gating, pipeline status sync, and release-preflight workflows.
    • Introduced CI failure handling and nightly release checks to streamline maintenance.
    • Added a standard CI workflow with Python version testing and linting.
  • Bug Fixes

    • Improved recovery for stalled bot-related PR review chains.
    • Added safeguards to keep review and merge status in sync with PR activity.
  • Chores

    • Updated internal checks and cleaned up unused typing imports.

Merge gate, pipeline status, CI failure Claude, nightly release gate,
PyPI release workflow, and repo-specific gate-config for plugin lifecycle scope.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a comprehensive GitHub Actions automation suite for PraisonAI-Plugins: CI testing, bot-opened PR review chaining, merge-gate quiescence evaluation and auto-merge, CI-failure detection with Claude fix triggers, pipeline status label syncing, and release-gate/PyPI publish automation, each with helper scripts and self-tests, plus minor unused-import cleanups.

Changes

CI and Merge Automation Pipeline

Layer / File(s) Summary
Shared gate configuration
.github/scripts/gate-config.js
Exports repo scope, sensitive path patterns, CI check patterns, package paths, Claude instructions, and reviewer bot allowlist used across scripts.
Merge-gate core logic
.github/scripts/merge-gate.js, .github/scripts/merge-gate-selftest.js
Implements Claude trigger/stale-FINAL detection, CI check evaluation, policy reason generators, PR context assembly, quiescence evaluation, candidate selection, verdict extraction, and validates behavior via a self-test suite.
PR review chain orchestration
.github/scripts/pr-review-chain.js
Gates Copilot and Claude FINAL triggering on prior reviewer signals (CodeRabbit, Greptile, Qodo, Gemini) with polling and comment posting.
Bot-opened PR review kickoff
.github/scripts/bot-pr-review-chain.js
Detects bot-opened PRs, posts CodeRabbit/Qodo kick comments, locates PRs by issue, and recovers stalled bot PRs.
CI failure detection and Claude CI-fix trigger
.github/scripts/ci-failure-claude.js, .github/scripts/ci-failure-claude-selftest.js, .github/workflows/ci-failure-claude.yml
Parses pytest failures, builds CI-fix comments, manages pending labels/cooldowns, dispatches @claude CI-fix comments, tested via self-test and wired into a workflow.
Pipeline status label sync
.github/scripts/pipeline-status.js, .github/scripts/pipeline-status-selftest.js, .github/workflows/pipeline-status-sync.yml
Derives stage/blocker labels, syncs PR labels, triggers/clears CI-fix state, and dispatches merge-gate for the oldest ready PR on a schedule.
Merge-gate workflow wiring
.github/workflows/claude-merge-gate.yml
Orchestrates auto-dispatch, candidate scanning, per-PR Claude assessment producing MERGE_GATE_VERDICT, and gated auto-merge with retries.
Auto PR comment review chain workflow
.github/workflows/auto-pr-comment.yml
Updates triggers/permissions, standardizes GH_TOKEN usage, adds Claude fallback-timeout and recovery jobs, and syncs pipeline labels after Claude triggers.
Release gate and PyPI publish automation
.github/scripts/release-gate.js, .github/scripts/release-gate-selftest.js, .github/workflows/nightly-release-gate.yml, .github/workflows/pypi-release.yml
Computes version bumps, checks PyPI/CI/tag state, enforces once-per-day releases, and dispatches/executes PyPI publishing with tagging.
Base CI workflow, secrets sync, and cleanup
.github/workflows/ci.yml, .github/workflows/sync-secrets-from-praisonai.yml, src/praisonai_plugins/integrations/slack_integration.py, src/praisonai_plugins/observability/gateway_forensics.py, src/praisonai_plugins/policies/strict_policy.py
Adds a base lint/test CI workflow, a cross-repo secrets sync dispatcher, and removes unused typing imports.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PR as Pull Request
  participant ReviewChain as pr-review-chain.js
  participant MergeGate as merge-gate.js
  participant PipelineStatus as pipeline-status.js
  participant ClaudeGate as claude-merge-gate.yml
  participant GitHub

  PR->>ReviewChain: advanceReviewChain
  ReviewChain->>GitHub: post Copilot / `@claude` FINAL comments
  PipelineStatus->>MergeGate: evaluatePipelineQuiescent
  MergeGate-->>PipelineStatus: ready, reasons
  PipelineStatus->>GitHub: sync stage/blocker labels
  PipelineStatus->>ClaudeGate: dispatchMergeGateForOldestReady
  ClaudeGate->>GitHub: post MERGE_GATE_VERDICT
  ClaudeGate->>GitHub: merge PR when approved and ready
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an automation pipeline with merge gate, CI, and PyPI release workflows.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch infra/automation-pipeline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Unblocks CI introduced by the automation pipeline workflow.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a suite of GitHub Actions automation scripts under .github/scripts/ to manage PR review chains, CI failure responses, merge gates, pipeline status syncing, and release preflights. The reviewer feedback highlights several opportunities to improve robustness by using optional chaining (?.) when accessing properties of potentially undefined objects (such as c.user or GraphQL query results) to prevent runtime TypeError crashes. Additionally, a minor copy-paste leftover in an error message within release-gate.js was identified for cleanup.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread .github/scripts/merge-gate.js Outdated

function isFinalClaudeTriggerComment(c) {
const body = (c.body || '').toLowerCase();
if (!AUTO_ACTORS.includes(c.user.login)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Directly accessing c.user.login without optional chaining can cause a TypeError if c.user is null or undefined (e.g., when a user account is deleted or for certain system actors). Using optional chaining c.user?.login ensures defensive programming and prevents the script from crashing.

Suggested change
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!AUTO_ACTORS.includes(c.user?.login)) return false;

Comment thread .github/scripts/merge-gate.js Outdated
function hasRecentClaudeTrigger(comments, minutes = 35) {
const cutoff = Date.now() - minutes * 60 * 1000;
return comments.some((c) => {
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Directly accessing c.user.login without optional chaining can cause a TypeError if c.user is null or undefined. Using optional chaining c.user?.login ensures defensive programming and prevents the script from crashing.

Suggested change
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user?.login)) return false;

Comment thread .github/scripts/merge-gate.js Outdated
}

function isConflictRebaseTriggerComment(c) {
if (!AUTO_ACTORS.includes(c.user.login)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Directly accessing c.user.login without optional chaining can cause a TypeError if c.user is null or undefined. Using optional chaining c.user?.login ensures defensive programming and prevents the script from crashing.

Suggested change
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!AUTO_ACTORS.includes(c.user?.login)) return false;

Comment thread .github/scripts/merge-gate.js Outdated
});
if (claudeRepliedAfterFinal) return false;
const claudeSinceHead = comments.some((c) => {
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Directly accessing c.user.login without optional chaining can cause a TypeError if c.user is null or undefined. Using optional chaining c.user?.login ensures defensive programming and prevents the script from crashing.

Suggested change
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user?.login)) return false;

Comment thread .github/scripts/merge-gate.js Outdated
Comment on lines +283 to +292
const prGql = result.repository.pullRequest;
const status = (prGql?.mergeStateStatus || '').toUpperCase();
if (status && status !== 'UNKNOWN') {
return {
status,
isDraft: prGql.isDraft,
headRepo: prGql.headRef?.repository?.nameWithOwner,
headSha: prGql.headRefOid,
maintainerCanModify: prGql.maintainerCanModify === true,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Accessing properties of result.repository and prGql directly without optional chaining can throw a TypeError if the GraphQL query fails, returns partial errors, or if the pull request cannot be found. Using optional chaining (result?.repository?.pullRequest and prGql?.isDraft, etc.) makes the code more robust and prevents unhandled exceptions.

Suggested change
const prGql = result.repository.pullRequest;
const status = (prGql?.mergeStateStatus || '').toUpperCase();
if (status && status !== 'UNKNOWN') {
return {
status,
isDraft: prGql.isDraft,
headRepo: prGql.headRef?.repository?.nameWithOwner,
headSha: prGql.headRefOid,
maintainerCanModify: prGql.maintainerCanModify === true,
};
const prGql = result?.repository?.pullRequest;
const status = (prGql?.mergeStateStatus || '').toUpperCase();
if (status && status !== 'UNKNOWN') {
return {
status,
isDraft: prGql?.isDraft,
headRepo: prGql?.headRef?.repository?.nameWithOwner,
headSha: prGql?.headRefOid,
maintainerCanModify: prGql?.maintainerCanModify === true,
};
}

Comment thread .github/scripts/merge-gate.js Outdated
Comment on lines +300 to +301
headRepo: pr.head.repo?.full_name,
headSha: pr.head.sha,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If pr.head is null or undefined (e.g., if the head branch or fork repository was deleted), accessing pr.head.sha directly will throw a TypeError. Using optional chaining pr.head?.sha ensures safe property access.

Suggested change
headRepo: pr.head.repo?.full_name,
headSha: pr.head.sha,
headRepo: pr.head?.repo?.full_name,
headSha: pr.head?.sha,

Comment thread .github/scripts/ci-failure-claude.js Outdated
function hasCiFixCommentForSha(comments, headSha) {
const marker = shortSha(headSha).toLowerCase();
return comments.some((c) => {
if (!AUTO_ACTORS.includes(c.user.login)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Directly accessing c.user.login without optional chaining can cause a TypeError if c.user is null or undefined. Using optional chaining c.user?.login ensures defensive programming and prevents the script from crashing.

Suggested change
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!AUTO_ACTORS.includes(c.user?.login)) return false;

Comment thread .github/scripts/ci-failure-claude.js Outdated
const cutoff = Date.now() - COOLDOWN_MS;
const marker = shortSha(headSha).toLowerCase();
return comments.some((c) => {
if (!AUTO_ACTORS.includes(c.user.login)) return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Directly accessing c.user.login without optional chaining can cause a TypeError if c.user is null or undefined. Using optional chaining c.user?.login ensures defensive programming and prevents the script from crashing.

Suggested change
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!AUTO_ACTORS.includes(c.user?.login)) return false;

Comment thread .github/scripts/release-gate.js Outdated
const path = require('path');
const toml = fs.readFileSync(path.join(root, 'pyproject.toml'), 'utf8');
const match = toml.match(/^version\s*=\s*"([^"]+)"/m);
if (!match) throw new Error('Could not read aiui version from pyproject.toml');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message mentions 'aiui version', which appears to be a copy-paste leftover from the PraisonAIUI repository. It should be updated to refer to the correct package or simply 'version' to avoid confusion.

Suggested change
if (!match) throw new Error('Could not read aiui version from pyproject.toml');
if (!match) throw new Error('Could not read version from pyproject.toml');

@greptile-apps

greptile-apps Bot commented Jul 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR ports the PraisonAIUI automation pipeline to the Plugins repo, adding a merge gate, CI failure → Claude trigger, nightly PyPI release gate, pipeline status label sync, a unified CI workflow, and a customised review chain. Three Python plugin files receive minor cleanup (unused import removals).

  • Release pipeline (pypi-release.yml, nightly-release-gate.yml): introduces a fully automated nightly patch release path with CI-SHA validation, once-per-day guard, and PyPI dedupe checks.
  • Review chain (auto-pr-comment.yml, pr-review-chain.js, bot-pr-review-chain.js): orchestrates CodeRabbit → Qodo/Gemini → Copilot → Claude FINAL with idempotency guards and stale-FINAL recovery via pull_request_target.
  • Merge gate (merge-gate.js, claude-merge-gate.yml): large shared helper ported from PraisonAIUI with secret-pattern scanning, conflict quiescence, and pipeline/* label management.

Confidence Score: 3/5

Safe to review but two concrete runtime defects need fixing before the pipeline is fully operational.

The sync-secrets workflow will always fail because GITHUB_TOKEN cannot dispatch workflows in a foreign repository. Separately, in pypi-release.yml the git tag is stamped before the rebase, so when a concurrent commit lands on main the tag ends up referencing an orphaned commit rather than the version actually shipped.

.github/workflows/sync-secrets-from-praisonai.yml (cross-repo token scope) and .github/workflows/pypi-release.yml (tag-before-rebase ordering).

Important Files Changed

Filename Overview
.github/workflows/sync-secrets-from-praisonai.yml Hub trigger workflow will always fail at runtime: uses GITHUB_TOKEN to dispatch a workflow in a different repository (MervinPraison/PraisonAI), which the token is not scoped for.
.github/workflows/pypi-release.yml New PyPI release workflow with two ordering bugs: publish before commit push (previously flagged), and git tag set before rebase causing tag to reference orphaned commit under concurrent-push conditions.
.github/workflows/auto-pr-comment.yml Substantially expanded review chain workflow; correctly adds pull_request_target trigger for stale-FINAL recovery.
.github/workflows/nightly-release-gate.yml Nightly/CI-triggered release preflight. Correctly pins the checkout action by SHA and handles both CI and nightly cron paths.
.github/scripts/merge-gate.js Large merge-gate helper script ported from PraisonAIUI. Secret scanning patterns, stale-FINAL recovery, conflict quiescence, and CI-fix deduplication logic are all present.
.github/scripts/pr-review-chain.js Review chain orchestrator. The hasGreptileReview heuristic uses a body-length fallback that could prematurely satisfy the greptile gate on error/status comments.
.github/scripts/gate-config.js Repo-scoped configuration for the pipeline. Paths, patterns, and scope strings are correctly adapted for the Plugins repo.
.github/scripts/bot-pr-review-chain.js Idempotent CodeRabbit/Qodo kick for bot-opened PRs. The qodoKickPosted check uses trim() === '/review' (previously noted improvement already in place).
.github/workflows/ci.yml New CI workflow adds ruff lint + pytest matrix across Python 3.10-3.12. Straightforward and correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    PR_OPEN([PR opened or synchronized]) --> CR[CodeRabbit posts summary]
    PR_OPEN --> BOT_PR{Bot PR?}
    BOT_PR -- Yes --> KICK[bot-pr-review-chain kicks CodeRabbit and Qodo]
    BOT_PR -- No --> WAIT_CR[Wait for CodeRabbit or Qodo]
    CR --> COPILOT[Trigger copilot via PAT as MervinPraison]
    KICK --> CR
    COPILOT --> POLL[Poll for Copilot response 30s x 20 attempts]
    POLL -- responded --> CLAUDE_FINAL[Post at-claude FINAL]
    POLL -- timeout --> CLAUDE_FINAL
    SCHEDULE([Scheduled or pull_request_target sync]) --> RECOVERY[claude-review-recovery stale FINAL check]
    RECOVERY -- stale or missing --> CLAUDE_FINAL
    CLAUDE_FINAL --> PIPELINE_LABEL[Sync pipeline labels]
    PIPELINE_LABEL --> MERGE_GATE{pipeline/merge-ready?}
    MERGE_GATE -- Yes --> AUTO_MERGE[claude-merge-gate auto-merge PR]
    MERGE_GATE -- No --> BLOCKED[blocked:ci or conflict or manual-review or cooldown]
    CI_FAIL([CI fails on PR]) --> CI_FIX[ci-failure-claude posts at-claude CI fix comment]
    CI_GREEN([CI green on main]) --> NIGHTLY[nightly-release-gate preflight]
    NIGHTLY -- pass --> PYPI[pypi-release bump then publish then commit then tag]
    NIGHTLY -- fail --> SKIP[Skip release]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    PR_OPEN([PR opened or synchronized]) --> CR[CodeRabbit posts summary]
    PR_OPEN --> BOT_PR{Bot PR?}
    BOT_PR -- Yes --> KICK[bot-pr-review-chain kicks CodeRabbit and Qodo]
    BOT_PR -- No --> WAIT_CR[Wait for CodeRabbit or Qodo]
    CR --> COPILOT[Trigger copilot via PAT as MervinPraison]
    KICK --> CR
    COPILOT --> POLL[Poll for Copilot response 30s x 20 attempts]
    POLL -- responded --> CLAUDE_FINAL[Post at-claude FINAL]
    POLL -- timeout --> CLAUDE_FINAL
    SCHEDULE([Scheduled or pull_request_target sync]) --> RECOVERY[claude-review-recovery stale FINAL check]
    RECOVERY -- stale or missing --> CLAUDE_FINAL
    CLAUDE_FINAL --> PIPELINE_LABEL[Sync pipeline labels]
    PIPELINE_LABEL --> MERGE_GATE{pipeline/merge-ready?}
    MERGE_GATE -- Yes --> AUTO_MERGE[claude-merge-gate auto-merge PR]
    MERGE_GATE -- No --> BLOCKED[blocked:ci or conflict or manual-review or cooldown]
    CI_FAIL([CI fails on PR]) --> CI_FIX[ci-failure-claude posts at-claude CI fix comment]
    CI_GREEN([CI green on main]) --> NIGHTLY[nightly-release-gate preflight]
    NIGHTLY -- pass --> PYPI[pypi-release bump then publish then commit then tag]
    NIGHTLY -- fail --> SKIP[Skip release]
Loading

Reviews (2): Last reviewed commit: "Harden automation scripts: optional chai..." | Re-trigger Greptile

git tag -f "${TAG}"
git pull --rebase origin main
git push origin main
git push --tags -f

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Force-pushing tags silently overwrites immutable version history

git push --tags -f force-pushes every local tag, so if a tag like v0.3.128 was already pushed (e.g., by a previous run that failed mid-way), this will silently move it to a different commit. A lost tag makes it impossible to reproduce that build from source. Using --force-with-lease or omitting -f and letting the push fail on duplicate tags is safer; the job can then exit with a clear error rather than corrupting tag history.

Suggested change
git push --tags -f
git push --tags

Comment thread .github/scripts/bot-pr-review-chain.js
Comment on lines 132 to 221
core.setOutput('triggered', 'true');
core.setOutput('pr_number', context.issue.number.toString());

# ================================================================
# FALLBACK: Claude trigger for cases where Copilot chain fails/skips
# Ensures Claude always runs after sufficient review time
# ================================================================
claude-fallback-timeout:
if: |
github.event_name == 'pull_request' &&
github.event.action == 'opened'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
issues: write
pull-requests: write
steps:
- name: Wait for review window (15 min)
run: sleep 900

- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
PR_NUMBER: ${{ github.event.pull_request.number }}
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c =>
c.body.includes('@claude') && triggerLogins.includes(c.user.login)
);
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
core.setOutput('comments_count', comments.data.length.toString());

const botReviews = comments.data.filter(c =>
['coderabbitai[bot]', 'qodo-code-review[bot]', 'gemini-code-assist[bot]',
'copilot-swe-agent', 'Copilot', 'greptile-apps[bot]'].some(bot =>
c.user.login.toLowerCase().includes(bot.toLowerCase())
)
);
core.setOutput('review_count', botReviews.length.toString());

- name: Aggregate reviews and trigger Claude
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const comments = await github.rest.issues.listComments({issue_number: pr, owner: context.repo.owner, repo: context.repo.repo, per_page: 100});
const reviews = {coderabbit: [], qodo: [], gemini: [], copilot: [], greptile: [], others: []};
comments.data.forEach(c => {
const login = c.user.login.toLowerCase();
const body = c.body.substring(0, 500);
if (login.includes('coderabbit')) reviews.coderabbit.push(body);
else if (login.includes('qodo')) reviews.qodo.push(body);
else if (login.includes('gemini')) reviews.gemini.push(body);
else if (login.includes('copilot')) reviews.copilot.push(body);
else if (login.includes('greptile')) reviews.greptile.push(body);
else if (c.user.type === 'Bot') reviews.others.push(body);
});
const summaryParts = [];
if (reviews.coderabbit.length) summaryParts.push('CodeRabbit: ' + reviews.coderabbit.length + ' comments');
if (reviews.qodo.length) summaryParts.push('Qodo: ' + reviews.qodo.length + ' comments');
if (reviews.gemini.length) summaryParts.push('Gemini: ' + reviews.gemini.length + ' comments');
if (reviews.copilot.length) summaryParts.push('Copilot: ' + reviews.copilot.length + ' comments');
if (reviews.greptile.length) summaryParts.push('Greptile: ' + reviews.greptile.length + ' comments');
const reviewSummary = summaryParts.length ? summaryParts.join(' | ') : 'No bot reviews detected';
const scope = process.env.CLAUDE_UI_SCOPE;
const phases = process.env.CLAUDE_FINAL_PHASES_COMPACT;
const claudeBody = '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI-Plugins (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL prior reviewer comments. ' + reviewSummary + '.\n\n' +
'Review Context: ' + comments.data.length + ' total comments, ' +
(reviews.coderabbit.length + reviews.qodo.length + reviews.gemini.length + reviews.copilot.length + reviews.greptile.length) + ' bot reviews.\n\n' +
phases;
await github.rest.issues.createComment({issue_number: pr, owner: context.repo.owner, repo: context.repo.repo, body: claudeBody});
console.log('Claude fallback triggered on PR #' + pr + ' with ' + reviewSummary);

# ================================================================
# Claude FINAL review — polls for Copilot's response, then triggers
# This runs IN-BAND under the PAT context (MervinPraison), avoiding

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 claude-fallback-timeout blocks a runner for 15 minutes per PR open event

The job runs an unconditional sleep 900 on a GitHub-hosted runner, consuming 15 minutes of billing time per PR regardless of whether the Claude fallback is ultimately needed. There is also no concurrency group on this job, so a burst of PR-open events could leave many sleeping runners idle simultaneously. The existing post-missing-claude-final scheduled job serves the same purpose without holding a runner slot.

Comment on lines +243 to +253
skip: true,
reason: 'head pushed soon after FINAL (wait for CI / batched fixes)',
};
}
const windowStart = Date.now() - STALE_FINAL_RECOVERY_WINDOW_MS;
const finalsInWindow = countFinalTriggersSince(comments, windowStart);
if (finalsInWindow >= STALE_FINAL_MAX_PER_WINDOW) {
return {
skip: true,
reason: `stale-FINAL capped (${finalsInWindow} FINAL triggers in last hour)`,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 hasRecentCiFixComment 12-hour cooldown applies across all SHAs

The function returns true (suppress new CI-fix comment) if any CI-fix comment by an auto-actor exists within the last 12 hours, even when the comment was for a completely different commit SHA. Because hasCiFixCommentForSha (checked first in shouldSkipCiFix) already handles the exact-SHA case, the broad time-based branch here silences assistance for subsequent pushes that fix one failing test but introduce a new one. Tying the cooldown to the head SHA rather than wall-clock time alone would ensure each distinct commit can receive at most one automated CI-fix comment.

@MervinPraison

Copy link
Copy Markdown
Owner Author

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI-Plugins (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAI-Plugins (src/praisonai_plugins, tests). Do not expand into praisonaiagents or the monorepo unless the issue explicitly requires it. Read ALL prior reviewer comments. CodeRabbit: 1 comments | Qodo: 1 comments | Greptile: 1 comments.

Review Context: 3 total comments, 3 bot reviews.

Phase 1: Review per AGENTS.md (lazy imports, backward compat, import-time <200ms; SDK/product value gate — genuine plugin lifecycle value, correct plugin vs SDK vs Tools layering, no scope creep)
Phase 2: FIX valid issues found by prior reviewers — push to THIS branch
Phase 3: Final verdict — approve PR if ready, else request changes

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MervinPraison's task —— View job


FINAL Architecture Review — PR #5

Todo

  • Read flagged script files
  • Fix optional chaining in merge-gate.js (Gemini: L54, 77, 85, 188, 292, 301)
  • Fix optional chaining in ci-failure-claude.js (Gemini: L31, 41)
  • Fix release-gate.js copy-paste error message (Gemini: L24)
  • Tighten qodoKickPosted in bot-pr-review-chain.js (Greptile)
  • Commit + push to infra/automation-pipeline
  • Final verdict

Phase 1 — Layer & scope review

Correct layering. The Python changes are pure unused-import cleanups (Optional/List/Dict/Any) that are correct and non-breaking; no plugin lifecycle logic changed, so lazy-import/import-time/backward-compat guarantees are untouched. gate-config.js is properly scoped to this repo (src/praisonai_plugins/, praisonai.plugins, praisonai.sandbox) with no core-SDK vendoring. This is infra + import hygiene only — no scope creep.

Phase 2 — Fixes pushed to this branch (commit 4b0b0bd)

File Fix Reviewer
merge-gate.js c.user?.login in 4 predicates + optional chaining on GraphQL result?.repository?.pullRequest and REST pr.head?.sha/pr.head?.repo Gemini (P1)
ci-failure-claude.js c.user?.login in hasCiFixCommentForSha / hasRecentCiFixComment Gemini (P1)
release-gate.js "aiui version""version" in error message Gemini (P2)
bot-pr-review-chain.js qodoKickPosted now matches body.trim() === '/review' (exact) instead of substring, avoiding false positives Greptile (P2)

These prevent TypeError crashes when c.user/pr.head/GraphQL results are null (deleted accounts, system actors, deleted head branch/fork, partial GraphQL errors).

Deliberately NOT changed (with reasoning)

  • hasRecentCiFixComment 12h wall-clock cooldown (Greptile P2): the exact-SHA path (hasCiFixCommentForSha) is checked first in shouldSkipCiFix; the time branch is an intentional rate-limiter guarding against comment spam when HEAD churns rapidly. Removing it changes throttling semantics, so I left it as-is by design rather than silently altering behavior.
  • .github/workflows/* items — I cannot modify workflow files (GitHub App permission). The following remain valid manual follow-ups:
    • pypi-release.yml: publish runs before the version-bump push → on push failure, PyPI is live but repo pyproject.toml stays stale and the dedupe gate blocks future retries. Recommend commit+push version bump first, then build+publish.
    • pypi-release.yml: git push --tags -f → prefer --force-with-lease or drop -f to avoid rewriting immutable tag history.
    • auto-pr-comment.yml: claude-fallback-timeout sleep 900 holds a runner 15 min/PR with no concurrency group; the scheduled post-missing-claude-final job already covers this.

Phase 3 — Verdict

Approve-ready for the script/Python layer once CI (ruff + pytest 3.10–3.12 + selftests) passes on 4b0b0bd. The three workflow-YAML items above are non-blocking operational rough edges but should be addressed by a maintainer before the pipeline runs on production traffic (the PyPI publish-before-push ordering being the most important).

Note: I could not execute node .github/scripts/*-selftest.js locally (node not in allowed tools); all edits are mechanical ?./exact-match changes and will be validated by the CI selftest step.

--- • branch infra/automation-pipeline

Apply reviewer feedback (Gemini, Greptile):
- merge-gate.js / ci-failure-claude.js: use c.user?.login and
  optional chaining on GraphQL/REST merge-state to avoid TypeError
  when user/head objects are null (deleted accounts, system actors,
  partial GraphQL errors, deleted head branch/fork).
- release-gate.js: fix copy-paste "aiui version" error message.
- bot-pr-review-chain.js: qodoKickPosted now matches full body
  trim() === '/review' instead of broad substring, preventing false
  positives from unrelated comments mentioning "/review".

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/auto-pr-comment.yml (1)

49-66: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Skip Copilot for bot-authored PRs in the generic Copilot trigger jobs.

The new bot PR path posts Claude directly, but copilot-after-coderabbit and copilot-after-qodo still match bot-authored PRs and can post @copilot before/alongside the direct Claude path. Add an author check before posting Copilot so bot PRs stay on the intended CodeRabbit/Qodo → Claude route.

Proposed fix
             const comments = await github.rest.issues.listComments({
               issue_number: context.issue.number,
               owner: context.repo.owner,
               repo: context.repo.repo
             });
+            const { data: pull } = await github.rest.pulls.get({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              pull_number: context.issue.number,
+            });
+            if (pull.user.type === 'Bot') {
+              console.log('Bot PR — direct Claude path handles review chain');
+              core.setOutput('triggered', 'false');
+              core.setOutput('pr_number', context.issue.number.toString());
+              return;
+            }

Apply the same guard in the Qodo-triggered Copilot step before posting @copilot.

Also applies to: 93-109, 671-725

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-pr-comment.yml around lines 49 - 66, The generic
Copilot trigger jobs still fire for bot-authored PRs, which can cause `@copilot`
to be posted alongside the direct Claude path. Add an author-based guard in the
Copilot posting logic for the `copilot-after-coderabbit` and
`copilot-after-qodo` workflows, using the existing trigger steps as the entry
points, so bot PRs are skipped and only the intended CodeRabbit/Qodo → Claude
route runs.
🟡 Minor comments (7)
.github/scripts/pr-review-chain.js-176-179 (1)

176-179: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t collapse the optional reviewer wait to zero by default.

priorReviewersReady() defaults to waiting for optional Qodo/Gemini signals, but Line 178 overrides an omitted value with 0, so the main orchestrator never waits unless callers remember to pass a timeout.

🐛 Proposed fix
   const chainOpts = {
     prCreatedAt: pr.created_at,
-    optionalWaitMs: options.optionalWaitMs ?? 0,
+    optionalWaitMs: options.optionalWaitMs,
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/pr-review-chain.js around lines 176 - 179, The main
orchestrator in pr-review-chain.js is forcing optionalWaitMs to 0 when it is
omitted, which disables the default waiting behavior in priorReviewersReady().
Update chainOpts so optionalWaitMs is only set when explicitly provided by
options, and otherwise let priorReviewersReady() use its own default wait logic.
.github/scripts/pr-review-chain.js-236-241 (1)

236-241: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Paginate pull reviews like issue comments.

Only the first 100 PR reviews are considered. Busy PRs can miss later Copilot/Qodo/Gemini review signals and block or mis-order the chain.

🐛 Proposed fix
-  const { data: reviews } = await github.rest.pulls.listReviews({
-    owner,
-    repo,
-    pull_number: prNumber,
-    per_page: 100,
-  });
+  let reviews;
+  if (typeof github.paginate === 'function') {
+    reviews = await github.paginate(github.rest.pulls.listReviews, {
+      owner,
+      repo,
+      pull_number: prNumber,
+      per_page: 100,
+    });
+  } else {
+    const { data } = await github.rest.pulls.listReviews({
+      owner,
+      repo,
+      pull_number: prNumber,
+      per_page: 100,
+    });
+    reviews = data;
+  }
   return { comments, reviews };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/pr-review-chain.js around lines 236 - 241, The pull review
fetch in `github.rest.pulls.listReviews` only reads the first page, so later
review signals can be missed on busy PRs. Update the review-loading logic in the
PR chain script to paginate through all review pages, similar to how issue
comments are handled, and aggregate every review before downstream processing.
Keep the change localized around the existing review retrieval flow so the chain
ordering and signal detection use the full set of reviews.
.github/scripts/bot-pr-review-chain.js-85-92 (1)

85-92: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Search all open PRs when resolving issue branches.

findOpenPrForIssue() only inspects the first 30 open PRs. An older matching bot PR can be missed and return no_pr even though it exists.

🐛 Proposed fix
-  const { data: prs } = await github.rest.pulls.list({
-    owner,
-    repo,
-    state: 'open',
-    sort: 'created',
-    direction: 'desc',
-    per_page: 30,
-  });
+  let prs;
+  if (typeof github.paginate === 'function') {
+    prs = await github.paginate(github.rest.pulls.list, {
+      owner,
+      repo,
+      state: 'open',
+      sort: 'created',
+      direction: 'desc',
+      per_page: 100,
+    });
+  } else {
+    const { data } = await github.rest.pulls.list({
+      owner,
+      repo,
+      state: 'open',
+      sort: 'created',
+      direction: 'desc',
+      per_page: 100,
+    });
+    prs = data;
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/bot-pr-review-chain.js around lines 85 - 92, The issue is
that findOpenPrForIssue only checks the first 30 open pull requests, so older
matching bot PRs can be skipped and incorrectly return no_pr. Update the PR
lookup in bot-pr-review-chain.js to page through all open PRs from
github.rest.pulls.list instead of relying on a single per_page batch, and keep
iterating until all pages are searched. Use the findOpenPrForIssue logic as the
entry point and preserve the existing matching behavior while expanding the
search scope.
.github/scripts/release-gate.js-64-75 (1)

64-75: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use the run update time for the UTC-day gate.

created_at is the workflow start time, so a successful run that finishes after midnight UTC won't be counted for that day. updated_at fits this check better.

Proposed fix
   return runs.data.workflow_runs.some(
-    (r) => r.conclusion === 'success' && new Date(r.created_at) >= dayStart
+    (r) => r.conclusion === 'success' && new Date(r.updated_at || r.created_at) >= dayStart
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/release-gate.js around lines 64 - 75, The UTC-day success
check in hasSuccessfulReleaseToday currently uses the workflow run creation
time, which can miss runs that complete after midnight UTC. Update the timestamp
comparison in hasSuccessfulReleaseToday to use the run update time instead of
created_at, keeping the rest of the listWorkflowRuns and success/conclusion
logic the same.
.github/scripts/ci-failure-claude.js-19-20 (1)

19-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the CI-fix SHA marker formatting.

ciFixShaMarker() currently returns ci failed on head \` without the closing backtick, so any caller comparing against the generated marker will not match the comment text built on Line 126.

Proposed fix
 function ciFixShaMarker(headSha) {
-  return `ci failed on head \`${shortSha(headSha)}`.toLowerCase();
+  return `ci failed on head \`${shortSha(headSha)}\``.toLowerCase();
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/ci-failure-claude.js around lines 19 - 20, The
ciFixShaMarker() output is missing the closing backtick around the SHA, so the
generated marker text won’t match the comment body used elsewhere. Update
ciFixShaMarker() to return the full quoted marker string with matching backticks
and keep the same shortSha(headSha) formatting so any caller comparing against
it will match correctly.
.github/scripts/pipeline-status.js-143-145 (1)

143-145: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Only remove labels managed by this workflow.

Filtering by pipeline/ removes any future/manual label in that namespace. Use ALL_PIPELINE_LABELS so sync only mutates labels this script owns.

Proposed fix
   const current = ctx.labels.filter((l) => l.startsWith(PIPELINE_PREFIX));
   const desired = new Set(all);
-  const toRemove = current.filter((l) => !desired.has(l) && l !== 'pipeline/merged');
+  const managed = new Set(ALL_PIPELINE_LABELS);
+  const toRemove = current.filter(
+    (l) => managed.has(l) && !desired.has(l) && l !== 'pipeline/merged'
+  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/pipeline-status.js around lines 143 - 145, The label cleanup
logic in the pipeline status sync is too broad because it filters by the
pipeline/ prefix instead of only the labels this workflow owns. Update the label
removal flow in pipeline-status.js to use ALL_PIPELINE_LABELS as the source of
truth, and compute toRemove only from that managed set while preserving
pipeline/merged. Keep the existing current and desired set comparison, but
restrict removals to labels declared in ALL_PIPELINE_LABELS so manual or future
namespace labels are not touched.
.github/scripts/ci-failure-claude.js-238-243 (1)

238-243: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use workflow job IDs here, not check-run IDs. mapFailedChecks() forwards check_runs[].id from checks.listForRef(), but downloadJobLogsForWorkflowRun({ job_id }) expects a workflow job id. That makes the log download fail and leaves failure extraction empty; fetch the matching job ids from the workflow-run jobs endpoint before calling fetchJobFailureSummary().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/ci-failure-claude.js around lines 238 - 243, The failure
summary loop is passing check-run IDs into fetchJobFailureSummary(), but
downloadJobLogsForWorkflowRun({ job_id }) needs workflow job IDs instead. Update
the failed-check mapping flow around mapFailedChecks(),
fetchJobFailureSummary(), and the failedRuns iteration to look up the matching
workflow-run job IDs from the jobs endpoint before building failureSummaries.
Then pass those job IDs into fetchJobFailureSummary() instead of check.id so log
downloads and failure extraction work correctly.
🧹 Nitpick comments (3)
.github/workflows/auto-pr-comment.yml (1)

534-534: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Disable checkout credential persistence in recovery/sweep jobs.

These jobs only need workspace files, not git credentials. Match the existing hardened checkout usage to avoid leaving tokens in .git/config.

Proposed fix
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false

Also applies to: 614-614

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-pr-comment.yml at line 534, The recovery/sweep jobs
still use actions/checkout without the hardened credential setting, so update
those checkout steps to match the existing secure pattern by disabling
credential persistence. Locate the checkout usages in the recovery/sweep job
sections and add the same persist-credentials=false behavior used elsewhere so
workspace files are checked out without leaving git tokens in .git/config.

Source: Linters/SAST tools

.github/scripts/release-gate.js (1)

140-154: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Avoid shell-building the path-filtered git diff.

PACKAGE_PATHS is config-controlled, but this still leaves correctness and command parsing dependent on shell escaping. Use execFileSync with argv.

Proposed fix
-  const { execSync } = require('child_process');
+  const { execFileSync, execSync } = require('child_process');
...
-    changed = execSync(
-      `git diff --name-only ${lastTag} HEAD -- ${PACKAGE_PATHS.join(' ')}`,
-      { encoding: 'utf8' }
-    ).trim();
+    changed = execFileSync(
+      'git',
+      ['diff', '--name-only', lastTag, 'HEAD', '--', ...PACKAGE_PATHS],
+      { encoding: 'utf8' }
+    ).trim();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/release-gate.js around lines 140 - 154, The git diff command
in release-gate.js is being built as a shell string, which makes command parsing
depend on escaping even though PACKAGE_PATHS is config-controlled. Update the
logic around execSync in the lastTag/changed calculation to use execFileSync
with explicit argv instead of interpolating the diff command, and keep the same
path filtering behavior by passing lastTag, HEAD, and the package paths as
separate arguments.

Source: Linters/SAST tools

.github/workflows/ci.yml (1)

18-18: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false on checkout.

The actions/checkout@v4 action persists the GITHUB_TOKEN credential in .git/config by default. Since this workflow only runs lint and tests (no push-back), there is no need for persisted credentials. zizmor flagged this as an artipacked risk.

🔒️ Recommended fix
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 18, Set the actions/checkout@v4 step to
disable credential persistence by adding persist-credentials: false, since this
CI workflow only runs lint and tests and does not need the GITHUB_TOKEN stored
in .git/config. Update the checkout step in the ci workflow so the action no
longer leaves reusable credentials behind.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/ci-failure-claude.js:
- Around line 203-206: The fork-permission check in the CI-failure automation is
too permissive because it allows non-base-repo PRs when maintainer edits are
enabled. Update the logic in the PR head repo guard that uses
pr.head.repo?.full_name, baseRepo, and pr.maintainer_can_modify so the workflow
only proceeds when the PR head repo matches the base repo, and skip all other
cases. Keep the existing skip response shape and reason handling in the same
ci-failure-claude.js flow.
- Around line 248-259: The label/comment sequence in `addLabels` and
`createComment` can strand `claude-ci-fix-pending` if the comment fails after
the label is added. Update the flow around `github.rest.issues.addLabels` and
`github.rest.issues.createComment` so the pending label is only left in place
when the comment is successfully created, and if comment creation throws, remove
the label or otherwise roll back the added state before exiting. Keep the fix
localized to the comment-posting path in `ci-failure-claude.js` so future runs
don’t skip forever on a stale pending label.

In @.github/scripts/pr-review-chain.js:
- Around line 167-169: The timeout fallback in advanceReviewChain() is
incorrectly using copilotTriggered(comments) alone to bypass Copilot, which can
cause an immediate Claude FINAL before Copilot has actually timed out. Update
the logic around the allowCopilotTimeout check so it only skips Copilot after a
real elapsed timeout has occurred, using the existing review-chain state and
timeout tracking near advanceReviewChain() and the timeout fallback setup in the
related block. Ensure copilotSkipped is set only when the timeout condition is
truly met, not just because a Copilot trigger comment exists.
- Around line 180-186: The Copilot trigger in pr-review-chain.js only checks
options.skipCopilot, so the REVIEW_CHAIN_SKIP_COPILOT setting is not honored
before posting `@copilot`. Update the Copilot gating in the main flow around
maybeTriggerCopilot to also respect the environment-based skip flag (the same
source used by downstream gating), and ensure the final copilot object reflects
a skipped state whenever either skip path is enabled.

In @.github/workflows/auto-pr-comment.yml:
- Around line 621-666: The scheduled/manual sweep in the workflow script runs
ps.syncPipelineLabels() without guaranteeing the pipeline labels exist first,
which can fail on fresh repos or after label deletion. Update the sweep loop to
call ps.ensurePipelineLabels() before ps.syncPipelineLabels(), using the
existing ps helper in the same block where PRs are processed so missing
pipeline/* labels are created before syncing.

In @.github/workflows/ci-failure-claude.yml:
- Around line 28-31: The workflow permissions block is missing repository
contents access, which can break actions/checkout under restricted tokens.
Update the permissions in the ci-failure-claude workflow to include contents:
read alongside the existing pull-requests, issues, and actions scopes so the
checkout step can access the repository contents. Make the same permission
change wherever the workflow defines this permissions block, including the
duplicated section later in the file.

In @.github/workflows/ci.yml:
- Around line 25-28: The CI install step is referencing an undefined `dev` extra
in the package install command, which should be corrected to match
`pyproject.toml`. Update the `Install dependencies` step in the workflow so it
either installs from a real optional-dependencies group after defining `dev` in
`pyproject.toml`, or remove the `.[dev]` suffix entirely since `ruff` and
`pytest-timeout` are already installed separately. Use the `pip install -e`
command in that workflow block as the locator for the change.

In @.github/workflows/claude-merge-gate.yml:
- Line 61: The workflow’s uses of actions/checkout@v4 are persisting the GitHub
token in git config, which is unsafe around PR code. Update each checkout step
in the merge gate workflow to set persist-credentials to false, and only provide
credentials explicitly in the later git fetch path if needed. Use the
actions/checkout invocations as the anchor points for the change.
- Around line 485-487: The merge call in the workflow is missing the approved
head SHA, leaving a TOCTOU gap between the earlier check and
github.rest.pulls.merge. Update the merge request to pass the validated
scanHeadSha as the sha argument alongside owner, repo, pull_number, and
merge_method so only the assessed commit can be merged.
- Around line 63-69: The GitHub App token requests in the workflow are scoped
only by owner, which gives access to all repositories in the installation.
Update each `actions/create-github-app-token@v1` step in the workflow by adding
the repository-scoping input alongside `owner`, using the same `repositories`
value for every token generation block so the token is limited to
`github.event.repository.name`.
- Around line 311-313: The fallback BLOCK step is guarded by an `if:` that still
inherits GitHub Actions’ implicit success check, so it won’t run when
`steps.assess` fails. Update the conditional on the `fallback_verdict` step in
`claude-merge-gate.yml` to include `always()` alongside `steps.assess.outcome !=
'success'` so the BLOCK comment posts even after assessment failures.

In @.github/workflows/pipeline-status-sync.yml:
- Around line 10-14: The workflow permissions block is missing repository
contents access, which can break actions/checkout in this job. Update the
permissions in pipeline-status-sync workflow to include contents: read alongside
the existing pull-requests, issues, actions, and id-token scopes, and make the
same change in the other permissions block referenced by the comment so checkout
has the required access.
- Around line 24-30: The GitHub App token is currently scoped only to the owner,
which makes it usable across all repositories in that installation. Update the
Generate GitHub App token step in pipeline-status-sync to include the
repositories input alongside owner, using github.event.repository.name, so the
token is limited to this repository before it is consumed by later workflow
steps.

In @.github/workflows/pypi-release.yml:
- Around line 83-112: The version bump logic in the versions step and the
pyproject.toml update step are using different matching rules, so the write path
can silently fail when formatting varies. Update the Bump pyproject.toml step to
use the same whitespace-tolerant matcher as the version-reading logic, and apply
the replacement based on that match instead of a fixed string. Keep the behavior
aligned with the existing versions output generation so current_version and
new_version remain consistent.
- Around line 27-31: The release workflow currently ignores the preflight
trigger SHA and instead checks out and rebases from main, which can publish an
ungated artifact. Update the release job in the workflow to honor
inputs.trigger_sha by either checking out that exact SHA directly or validating
it against origin/main before proceeding, and make the gate explicit in the
release flow so the publish step cannot continue on a mismatched commit.
- Around line 114-135: The release flow in the Publish to PyPI and Commit, tag,
and release steps should push the versioned VCS state before running uv publish.
Reorder the workflow so the commit, tag, and pushes happen first, then publish
to PyPI only after the git tag and GitHub release are safely created, using the
VERSION/TAG logic already in place. Also remove force-moving tag operations in
the release step by avoiding git tag -f and git push --tags -f, and use
non-forced tag creation/push so release tags remain immutable.

In @.github/workflows/sync-secrets-from-praisonai.yml:
- Around line 14-21: The sync-secrets workflow is using secrets.GITHUB_TOKEN to
call gh workflow run against MervinPraison/PraisonAI, but that token cannot
trigger workflows in another repository. Update the job in
sync-secrets-from-praisonai.yml to use a repository secret containing a PAT or
GitHub App token with access to MervinPraison/PraisonAI, and make sure the token
has the required repo and workflow scopes before invoking gh workflow run.

---

Outside diff comments:
In @.github/workflows/auto-pr-comment.yml:
- Around line 49-66: The generic Copilot trigger jobs still fire for
bot-authored PRs, which can cause `@copilot` to be posted alongside the direct
Claude path. Add an author-based guard in the Copilot posting logic for the
`copilot-after-coderabbit` and `copilot-after-qodo` workflows, using the
existing trigger steps as the entry points, so bot PRs are skipped and only the
intended CodeRabbit/Qodo → Claude route runs.

---

Minor comments:
In @.github/scripts/bot-pr-review-chain.js:
- Around line 85-92: The issue is that findOpenPrForIssue only checks the first
30 open pull requests, so older matching bot PRs can be skipped and incorrectly
return no_pr. Update the PR lookup in bot-pr-review-chain.js to page through all
open PRs from github.rest.pulls.list instead of relying on a single per_page
batch, and keep iterating until all pages are searched. Use the
findOpenPrForIssue logic as the entry point and preserve the existing matching
behavior while expanding the search scope.

In @.github/scripts/ci-failure-claude.js:
- Around line 19-20: The ciFixShaMarker() output is missing the closing backtick
around the SHA, so the generated marker text won’t match the comment body used
elsewhere. Update ciFixShaMarker() to return the full quoted marker string with
matching backticks and keep the same shortSha(headSha) formatting so any caller
comparing against it will match correctly.
- Around line 238-243: The failure summary loop is passing check-run IDs into
fetchJobFailureSummary(), but downloadJobLogsForWorkflowRun({ job_id }) needs
workflow job IDs instead. Update the failed-check mapping flow around
mapFailedChecks(), fetchJobFailureSummary(), and the failedRuns iteration to
look up the matching workflow-run job IDs from the jobs endpoint before building
failureSummaries. Then pass those job IDs into fetchJobFailureSummary() instead
of check.id so log downloads and failure extraction work correctly.

In @.github/scripts/pipeline-status.js:
- Around line 143-145: The label cleanup logic in the pipeline status sync is
too broad because it filters by the pipeline/ prefix instead of only the labels
this workflow owns. Update the label removal flow in pipeline-status.js to use
ALL_PIPELINE_LABELS as the source of truth, and compute toRemove only from that
managed set while preserving pipeline/merged. Keep the existing current and
desired set comparison, but restrict removals to labels declared in
ALL_PIPELINE_LABELS so manual or future namespace labels are not touched.

In @.github/scripts/pr-review-chain.js:
- Around line 176-179: The main orchestrator in pr-review-chain.js is forcing
optionalWaitMs to 0 when it is omitted, which disables the default waiting
behavior in priorReviewersReady(). Update chainOpts so optionalWaitMs is only
set when explicitly provided by options, and otherwise let priorReviewersReady()
use its own default wait logic.
- Around line 236-241: The pull review fetch in `github.rest.pulls.listReviews`
only reads the first page, so later review signals can be missed on busy PRs.
Update the review-loading logic in the PR chain script to paginate through all
review pages, similar to how issue comments are handled, and aggregate every
review before downstream processing. Keep the change localized around the
existing review retrieval flow so the chain ordering and signal detection use
the full set of reviews.

In @.github/scripts/release-gate.js:
- Around line 64-75: The UTC-day success check in hasSuccessfulReleaseToday
currently uses the workflow run creation time, which can miss runs that complete
after midnight UTC. Update the timestamp comparison in hasSuccessfulReleaseToday
to use the run update time instead of created_at, keeping the rest of the
listWorkflowRuns and success/conclusion logic the same.

---

Nitpick comments:
In @.github/scripts/release-gate.js:
- Around line 140-154: The git diff command in release-gate.js is being built as
a shell string, which makes command parsing depend on escaping even though
PACKAGE_PATHS is config-controlled. Update the logic around execSync in the
lastTag/changed calculation to use execFileSync with explicit argv instead of
interpolating the diff command, and keep the same path filtering behavior by
passing lastTag, HEAD, and the package paths as separate arguments.

In @.github/workflows/auto-pr-comment.yml:
- Line 534: The recovery/sweep jobs still use actions/checkout without the
hardened credential setting, so update those checkout steps to match the
existing secure pattern by disabling credential persistence. Locate the checkout
usages in the recovery/sweep job sections and add the same
persist-credentials=false behavior used elsewhere so workspace files are checked
out without leaving git tokens in .git/config.

In @.github/workflows/ci.yml:
- Line 18: Set the actions/checkout@v4 step to disable credential persistence by
adding persist-credentials: false, since this CI workflow only runs lint and
tests and does not need the GITHUB_TOKEN stored in .git/config. Update the
checkout step in the ci workflow so the action no longer leaves reusable
credentials behind.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52e51d6a-6d26-4c29-b91e-91a94ddd979b

📥 Commits

Reviewing files that changed from the base of the PR and between 8ea5f0e and 4b0b0bd.

📒 Files selected for processing (22)
  • .github/scripts/bot-pr-review-chain.js
  • .github/scripts/ci-failure-claude-selftest.js
  • .github/scripts/ci-failure-claude.js
  • .github/scripts/gate-config.js
  • .github/scripts/merge-gate-selftest.js
  • .github/scripts/merge-gate.js
  • .github/scripts/pipeline-status-selftest.js
  • .github/scripts/pipeline-status.js
  • .github/scripts/pr-review-chain.js
  • .github/scripts/release-gate-selftest.js
  • .github/scripts/release-gate.js
  • .github/workflows/auto-pr-comment.yml
  • .github/workflows/ci-failure-claude.yml
  • .github/workflows/ci.yml
  • .github/workflows/claude-merge-gate.yml
  • .github/workflows/nightly-release-gate.yml
  • .github/workflows/pipeline-status-sync.yml
  • .github/workflows/pypi-release.yml
  • .github/workflows/sync-secrets-from-praisonai.yml
  • src/praisonai_plugins/integrations/slack_integration.py
  • src/praisonai_plugins/observability/gateway_forensics.py
  • src/praisonai_plugins/policies/strict_policy.py

Comment on lines +203 to +206
const headRepo = pr.head.repo?.full_name;
if (headRepo && headRepo !== baseRepo && !pr.maintainer_can_modify) {
return { skipped: true, reason: 'fork without maintainer edits' };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep CI-fix automation internal-only.

This permits fork PRs when maintainer_can_modify is true, but the workflow is documented as internal-only and posts a privileged @claude CI-fix trigger. Require the PR head repo to be the base repo instead.

Proposed fix
   const headRepo = pr.head.repo?.full_name;
-  if (headRepo && headRepo !== baseRepo && !pr.maintainer_can_modify) {
-    return { skipped: true, reason: 'fork without maintainer edits' };
+  if (headRepo !== baseRepo) {
+    return { skipped: true, reason: 'external fork' };
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const headRepo = pr.head.repo?.full_name;
if (headRepo && headRepo !== baseRepo && !pr.maintainer_can_modify) {
return { skipped: true, reason: 'fork without maintainer edits' };
}
const headRepo = pr.head.repo?.full_name;
if (headRepo !== baseRepo) {
return { skipped: true, reason: 'external fork' };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/ci-failure-claude.js around lines 203 - 206, The
fork-permission check in the CI-failure automation is too permissive because it
allows non-base-repo PRs when maintainer edits are enabled. Update the logic in
the PR head repo guard that uses pr.head.repo?.full_name, baseRepo, and
pr.maintainer_can_modify so the workflow only proceeds when the PR head repo
matches the base repo, and skip all other cases. Keep the existing skip response
shape and reason handling in the same ci-failure-claude.js flow.

Comment on lines +248 to +259
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [CI_FIX_LABEL],
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid stranding claude-ci-fix-pending when comment creation fails.

If addLabels() succeeds but createComment() fails, later runs skip forever on the pending label while CI is still red.

Proposed fix
   await github.rest.issues.addLabels({
     owner,
     repo,
     issue_number: prNumber,
     labels: [CI_FIX_LABEL],
   });
-  await github.rest.issues.createComment({
-    owner,
-    repo,
-    issue_number: prNumber,
-    body,
-  });
+  try {
+    await github.rest.issues.createComment({
+      owner,
+      repo,
+      issue_number: prNumber,
+      body,
+    });
+  } catch (err) {
+    try {
+      await github.rest.issues.removeLabel({
+        owner,
+        repo,
+        issue_number: prNumber,
+        name: CI_FIX_LABEL,
+      });
+    } catch (removeErr) {
+      if (removeErr.status !== 404) core?.warning?.(`Failed to roll back ${CI_FIX_LABEL}: ${removeErr.message}`);
+    }
+    throw err;
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [CI_FIX_LABEL],
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body,
});
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [CI_FIX_LABEL],
});
try {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body,
});
} catch (err) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: CI_FIX_LABEL,
});
} catch (removeErr) {
if (removeErr.status !== 404) core?.warning?.(`Failed to roll back ${CI_FIX_LABEL}: ${removeErr.message}`);
}
throw err;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/ci-failure-claude.js around lines 248 - 259, The
label/comment sequence in `addLabels` and `createComment` can strand
`claude-ci-fix-pending` if the comment fails after the label is added. Update
the flow around `github.rest.issues.addLabels` and
`github.rest.issues.createComment` so the pending label is only left in place
when the comment is successfully created, and if comment creation throws, remove
the label or otherwise roll back the added state before exiting. Keep the fix
localized to the comment-posting path in `ci-failure-claude.js` so future runs
don’t skip forever on a stale pending label.

Comment on lines +167 to +169
if (options.allowCopilotTimeout && copilotTriggered(comments)) {
return { ready: true, reason: 'copilot timeout fallback', copilotSkipped: true };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require an actual elapsed timeout before bypassing Copilot.

Line 167 treats any existing @copilot trigger as a timeout. Since Line 196 enables timeout fallback by default, advanceReviewChain() can post Claude FINAL immediately after posting Copilot, before Copilot responds.

🐛 Proposed fix
-  if (options.allowCopilotTimeout && copilotTriggered(comments)) {
-    return { ready: true, reason: 'copilot timeout fallback', copilotSkipped: true };
+  if (options.allowCopilotTimeout) {
+    const trigger = copilotTriggerComment(comments);
+    const triggerTime = new Date(trigger?.created_at || '').getTime();
+    const timeoutMs = options.copilotTimeoutMs ?? 30 * 60 * 1000;
+    if (Number.isFinite(triggerTime) && Date.now() - triggerTime >= timeoutMs) {
+      return { ready: true, reason: 'copilot timeout fallback', copilotSkipped: true };
+    }
   }
     allowCopilotTimeout: options.allowCopilotTimeout !== false,
     allowStaleRepost: options.allowStaleRepost === true,
     skipCopilot: options.skipCopilot,
+    copilotTimeoutMs: options.copilotTimeoutMs,
   });

Also applies to: 193-199

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/pr-review-chain.js around lines 167 - 169, The timeout
fallback in advanceReviewChain() is incorrectly using copilotTriggered(comments)
alone to bypass Copilot, which can cause an immediate Claude FINAL before
Copilot has actually timed out. Update the logic around the allowCopilotTimeout
check so it only skips Copilot after a real elapsed timeout has occurred, using
the existing review-chain state and timeout tracking near advanceReviewChain()
and the timeout fallback setup in the related block. Ensure copilotSkipped is
set only when the timeout condition is truly met, not just because a Copilot
trigger comment exists.

Comment on lines +180 to +186
const copilot = options.skipCopilot
? { triggered: false, reason: 'skipped' }
: await maybeTriggerCopilot(github, owner, repo, prNumber, core, chainOpts);
const claude = await maybeTriggerClaudeFinal(
github, owner, repo, prNumber, finalBody, core,
{ ...options, ...chainOpts }
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Honor REVIEW_CHAIN_SKIP_COPILOT before posting Copilot.

Line 180 checks only options.skipCopilot, so REVIEW_CHAIN_SKIP_COPILOT=1 still posts an @copilot comment even though downstream gating treats Copilot as skipped.

🐛 Proposed fix
-  const copilot = options.skipCopilot
+  const skipCopilot = isSkipCopilot(options);
+  const copilot = skipCopilot
     ? { triggered: false, reason: 'skipped' }
     : await maybeTriggerCopilot(github, owner, repo, prNumber, core, chainOpts);
   const claude = await maybeTriggerClaudeFinal(
     github, owner, repo, prNumber, finalBody, core,
-    { ...options, ...chainOpts }
+    { ...options, ...chainOpts, skipCopilot }
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const copilot = options.skipCopilot
? { triggered: false, reason: 'skipped' }
: await maybeTriggerCopilot(github, owner, repo, prNumber, core, chainOpts);
const claude = await maybeTriggerClaudeFinal(
github, owner, repo, prNumber, finalBody, core,
{ ...options, ...chainOpts }
);
const skipCopilot = isSkipCopilot(options);
const copilot = skipCopilot
? { triggered: false, reason: 'skipped' }
: await maybeTriggerCopilot(github, owner, repo, prNumber, core, chainOpts);
const claude = await maybeTriggerClaudeFinal(
github, owner, repo, prNumber, finalBody, core,
{ ...options, ...chainOpts, skipCopilot }
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/pr-review-chain.js around lines 180 - 186, The Copilot
trigger in pr-review-chain.js only checks options.skipCopilot, so the
REVIEW_CHAIN_SKIP_COPILOT setting is not honored before posting `@copilot`. Update
the Copilot gating in the main flow around maybeTriggerCopilot to also respect
the environment-based skip flag (the same source used by downstream gating), and
ensure the final copilot object reflects a skipped state whenever either skip
path is enabled.

Comment on lines +621 to +666
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pr-review-chain.js'));
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const prs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100,
});
console.log(`Triggered CodeRabbit for bot PR #${pr}`);
let posted = 0;
for (const pr of prs) {
if (pr.draft) continue;
const comments = await mergeGate.listAllComments(github, owner, repo, pr.number);
const headPushedAt = (await mergeGate.getHeadCommitDate(github, owner, repo, pr.number)) || pr.updated_at;
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(comments);
const isStale = mergeGate.isStaleFinalAfterPush(comments, headPushedAt);

// 2. Trigger Qodo review
if (hasFinal && !isStale) continue;
if (mergeGate.hasRecentClaudeTrigger(comments, 10)) {
core.info(`PR #${pr.number}: @claude within 10min — skip`);
continue;
}
if (await mergeGate.hasInProgressClaudeAssistant(github, owner, repo, pr.number)) {
core.info(`PR #${pr.number}: Claude in progress — skip`);
continue;
}
if (hasFinal && isStale) {
const gate = mergeGate.shouldSkipStaleFinalRecovery(comments, headPushedAt);
if (gate.skip) {
core.info(`PR #${pr.number}: skip stale — ${gate.reason}`);
continue;
}
}

const opts = hasFinal && isStale
? { allowStaleRepost: true, skipCopilot: true, optionalWaitMs: 0 }
: { allowCopilotTimeout: true, optionalWaitMs: 0 };
const result = await chain.maybeTriggerClaudeFinal(
github, owner, repo, pr.number,
mergeGate.FINAL_CLAUDE_REVIEW_BODY, core, opts
);
if (result.posted) {
posted++;
core.info(`Posted Claude FINAL on PR #${pr.number}`);
}
await ps.syncPipelineLabels(github, owner, repo, pr.number, core);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Create pipeline labels before the scheduled/manual sweep syncs them.

This sweep calls ps.syncPipelineLabels(...) without first calling ps.ensurePipelineLabels(...). On a fresh repo or after labels are deleted, syncPipelineLabels can fail when adding missing pipeline/* labels.

Proposed fix
             const owner = context.repo.owner;
             const repo = context.repo.repo;
+            await ps.ensurePipelineLabels(github, owner, repo, core);
             const prs = await github.paginate(github.rest.pulls.list, {
               owner, repo, state: 'open', per_page: 100,
             });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pr-review-chain.js'));
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const prs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100,
});
console.log(`Triggered CodeRabbit for bot PR #${pr}`);
let posted = 0;
for (const pr of prs) {
if (pr.draft) continue;
const comments = await mergeGate.listAllComments(github, owner, repo, pr.number);
const headPushedAt = (await mergeGate.getHeadCommitDate(github, owner, repo, pr.number)) || pr.updated_at;
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(comments);
const isStale = mergeGate.isStaleFinalAfterPush(comments, headPushedAt);
// 2. Trigger Qodo review
if (hasFinal && !isStale) continue;
if (mergeGate.hasRecentClaudeTrigger(comments, 10)) {
core.info(`PR #${pr.number}: @claude within 10min — skip`);
continue;
}
if (await mergeGate.hasInProgressClaudeAssistant(github, owner, repo, pr.number)) {
core.info(`PR #${pr.number}: Claude in progress — skip`);
continue;
}
if (hasFinal && isStale) {
const gate = mergeGate.shouldSkipStaleFinalRecovery(comments, headPushedAt);
if (gate.skip) {
core.info(`PR #${pr.number}: skip stale — ${gate.reason}`);
continue;
}
}
const opts = hasFinal && isStale
? { allowStaleRepost: true, skipCopilot: true, optionalWaitMs: 0 }
: { allowCopilotTimeout: true, optionalWaitMs: 0 };
const result = await chain.maybeTriggerClaudeFinal(
github, owner, repo, pr.number,
mergeGate.FINAL_CLAUDE_REVIEW_BODY, core, opts
);
if (result.posted) {
posted++;
core.info(`Posted Claude FINAL on PR #${pr.number}`);
}
await ps.syncPipelineLabels(github, owner, repo, pr.number, core);
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pr-review-chain.js'));
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
await ps.ensurePipelineLabels(github, owner, repo, core);
const prs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100,
});
let posted = 0;
for (const pr of prs) {
if (pr.draft) continue;
const comments = await mergeGate.listAllComments(github, owner, repo, pr.number);
const headPushedAt = (await mergeGate.getHeadCommitDate(github, owner, repo, pr.number)) || pr.updated_at;
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(comments);
const isStale = mergeGate.isStaleFinalAfterPush(comments, headPushedAt);
if (hasFinal && !isStale) continue;
if (mergeGate.hasRecentClaudeTrigger(comments, 10)) {
core.info(`PR #${pr.number}: `@claude` within 10min — skip`);
continue;
}
if (await mergeGate.hasInProgressClaudeAssistant(github, owner, repo, pr.number)) {
core.info(`PR #${pr.number}: Claude in progress — skip`);
continue;
}
if (hasFinal && isStale) {
const gate = mergeGate.shouldSkipStaleFinalRecovery(comments, headPushedAt);
if (gate.skip) {
core.info(`PR #${pr.number}: skip stale — ${gate.reason}`);
continue;
}
}
const opts = hasFinal && isStale
? { allowStaleRepost: true, skipCopilot: true, optionalWaitMs: 0 }
: { allowCopilotTimeout: true, optionalWaitMs: 0 };
const result = await chain.maybeTriggerClaudeFinal(
github, owner, repo, pr.number,
mergeGate.FINAL_CLAUDE_REVIEW_BODY, core, opts
);
if (result.posted) {
posted++;
core.info(`Posted Claude FINAL on PR #${pr.number}`);
}
await ps.syncPipelineLabels(github, owner, repo, pr.number, core);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/auto-pr-comment.yml around lines 621 - 666, The
scheduled/manual sweep in the workflow script runs ps.syncPipelineLabels()
without guaranteeing the pipeline labels exist first, which can fail on fresh
repos or after label deletion. Update the sweep loop to call
ps.ensurePipelineLabels() before ps.syncPipelineLabels(), using the existing ps
helper in the same block where PRs are processed so missing pipeline/* labels
are created before syncing.

Comment on lines +24 to +30
- 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 }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,120p' .github/workflows/pipeline-status-sync.yml

Repository: MervinPraison/PraisonAI-Plugins

Length of output: 1348


🌐 Web query:

actions/create-github-app-token@v1 repositories input owner installation token scope default documentation

💡 Result:

In the actions/create-github-app-token action, the owner and repositories inputs work together to define the scope of the generated installation access token [1][2]. Their behavior is as follows: - owner: An optional input specifying the owner of the GitHub App installation. If left empty, it defaults to the current repository owner [1][3]. - repositories: An optional input accepting a comma or newline-separated list of repositories. If left unset, it defaults to the current repository [1][3]. The resulting token scope is determined by the combination of these inputs [1][2]: - If both owner and repositories are empty: The token is scoped only to the current repository [1][2]. - If owner is explicitly set and repositories is empty: The token is scoped to all repositories within that owner's installation [1][2]. - If repositories is set: The token is scoped specifically to those listed repositories [1][4]. Note that if the owner is unset, the repositories input defaults to the current repository, and if the owner is set, the repositories input defaults to all repositories within that owner's installation [1][5]. The token will always inherit all permissions configured for the GitHub App installation [4][2].

Citations:


Scope the GitHub App token to this repository. Passing only owner leaves the installation token valid for every repository in that owner’s installation. Add repositories: ${{ github.event.repository.name }} before using it here.

🧰 Tools
🪛 zizmor (1.26.1)

[error] 30-30: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)


[error] 26-26: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pipeline-status-sync.yml around lines 24 - 30, The GitHub
App token is currently scoped only to the owner, which makes it usable across
all repositories in that installation. Update the Generate GitHub App token step
in pipeline-status-sync to include the repositories input alongside owner, using
github.event.repository.name, so the token is limited to this repository before
it is consumed by later workflow steps.

Source: Linters/SAST tools

Comment on lines +27 to +31
trigger_sha:
description: 'SHA that triggered this release'
required: false
default: ''
type: string

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files .github/workflows/*.yml .github/workflows/*.yaml | sed 's#^`#-` #'

printf '\n== pypi-release workflow outline ==\n'
wc -l .github/workflows/pypi-release.yml
cat -n .github/workflows/pypi-release.yml | sed -n '1,260p'

printf '\n== nightly-release-gate workflow outline ==\n'
if [ -f .github/workflows/nightly-release-gate.yml ]; then
  wc -l .github/workflows/nightly-release-gate.yml
  cat -n .github/workflows/nightly-release-gate.yml | sed -n '1,260p'
fi

Repository: MervinPraison/PraisonAI-Plugins

Length of output: 11382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== workflow references to trigger_sha ==\n'
rg -n "trigger_sha|rebase|origin/main|checkout@v4|pypi release|nightly" .github/workflows -S

printf '\n== relevant sections around lines in pypi-release ==\n'
sed -n '1,220p' .github/workflows/pypi-release.yml

Repository: MervinPraison/PraisonAI-Plugins

Length of output: 7623


Honor trigger_sha before publishing. The gate workflow passes the preflight SHA, but this job ignores it, checks out main, and later rebases from origin/main. If main moves between preflight and release, PyPI can get an artifact that was never gated. Fail fast when inputs.trigger_sha doesn’t match origin/main, or check out that SHA directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pypi-release.yml around lines 27 - 31, The release
workflow currently ignores the preflight trigger SHA and instead checks out and
rebases from main, which can publish an ungated artifact. Update the release job
in the workflow to honor inputs.trigger_sha by either checking out that exact
SHA directly or validating it against origin/main before proceeding, and make
the gate explicit in the release flow so the publish step cannot continue on a
mismatched commit.

Comment on lines +83 to +112
content = Path("pyproject.toml").read_text()
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
if not match:
sys.exit("Could not read version from pyproject.toml")
current = match.group(1)
maj, min_, patch = map(int, current.split("."))
if override:
new_version = override
elif bump == "major":
new_version = f"{maj + 1}.0.0"
elif bump == "minor":
new_version = f"{maj}.{min_ + 1}.0"
else:
new_version = f"{maj}.{min_}.{patch + 1}"
with open(os.environ["GITHUB_OUTPUT"], "a") as fh:
fh.write(f"current_version={current}\nnew_version={new_version}\n")
PY

- name: Bump pyproject.toml
if: inputs.dry_run != true
env:
CURRENT: ${{ steps.versions.outputs.current_version }}
NEW_VERSION: ${{ steps.versions.outputs.new_version }}
run: |
python -c "
from pathlib import Path
import os
p = Path('pyproject.toml')
p.write_text(p.read_text().replace(f'version = \"{os.environ[\"CURRENT\"]}\"', f'version = \"{os.environ[\"NEW_VERSION\"]}\"', 1))
"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the same matcher for reading and writing the version.

The read regex accepts valid whitespace variants, but the write step only replaces version = "...". If formatting changes, the bump silently no-ops.

Proposed fix
-          p.write_text(p.read_text().replace(f'version = \"{os.environ[\"CURRENT\"]}\"', f'version = \"{os.environ[\"NEW_VERSION\"]}\"', 1))
+          import re, sys
+          content = p.read_text()
+          updated, count = re.subn(
+              r'^version\s*=\s*"[^"]+"',
+              f'version = "{os.environ["NEW_VERSION"]}"',
+              content,
+              count=1,
+              flags=re.MULTILINE,
+          )
+          if count != 1:
+              sys.exit("Could not update version in pyproject.toml")
+          p.write_text(updated)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
content = Path("pyproject.toml").read_text()
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
if not match:
sys.exit("Could not read version from pyproject.toml")
current = match.group(1)
maj, min_, patch = map(int, current.split("."))
if override:
new_version = override
elif bump == "major":
new_version = f"{maj + 1}.0.0"
elif bump == "minor":
new_version = f"{maj}.{min_ + 1}.0"
else:
new_version = f"{maj}.{min_}.{patch + 1}"
with open(os.environ["GITHUB_OUTPUT"], "a") as fh:
fh.write(f"current_version={current}\nnew_version={new_version}\n")
PY
- name: Bump pyproject.toml
if: inputs.dry_run != true
env:
CURRENT: ${{ steps.versions.outputs.current_version }}
NEW_VERSION: ${{ steps.versions.outputs.new_version }}
run: |
python -c "
from pathlib import Path
import os
p = Path('pyproject.toml')
p.write_text(p.read_text().replace(f'version = \"{os.environ[\"CURRENT\"]}\"', f'version = \"{os.environ[\"NEW_VERSION\"]}\"', 1))
"
- name: Bump pyproject.toml
if: inputs.dry_run != true
env:
CURRENT: ${{ steps.versions.outputs.current_version }}
NEW_VERSION: ${{ steps.versions.outputs.new_version }}
run: |
python -c "
from pathlib import Path
import os
import re, sys
p = Path('pyproject.toml')
content = p.read_text()
updated, count = re.subn(
r'^version\s*=\s*\"[^\"]+\"',
f'version = \"{os.environ[\"NEW_VERSION\"]}\"',
content,
count=1,
flags=re.MULTILINE,
)
if count != 1:
sys.exit(\"Could not update version in pyproject.toml\")
p.write_text(updated)
"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pypi-release.yml around lines 83 - 112, The version bump
logic in the versions step and the pyproject.toml update step are using
different matching rules, so the write path can silently fail when formatting
varies. Update the Bump pyproject.toml step to use the same whitespace-tolerant
matcher as the version-reading logic, and apply the replacement based on that
match instead of a fixed string. Keep the behavior aligned with the existing
versions output generation so current_version and new_version remain consistent.

Comment on lines +114 to +135
- name: Publish to PyPI
if: inputs.dry_run != true
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN || secrets.PYPI_API_TOKEN }}
run: |
uv lock
uv build
uv publish

- name: Commit, tag, and release
if: inputs.dry_run != true
env:
VERSION: ${{ steps.versions.outputs.new_version }}
run: |
TAG="v${VERSION}"
git add pyproject.toml uv.lock
git commit -m "Release ${TAG}" || exit 0
git tag -f "${TAG}"
git pull --rebase origin main
git push origin main
git push --tags -f
gh release create "${TAG}" --title "praisonai-plugins ${TAG}" --notes "Release ${TAG}" --latest

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Push immutable VCS state before publishing to PyPI.

uv publish runs before the release commit/tag is pushed, so a later git or GitHub release failure leaves an immutable PyPI artifact without matching VCS state. Also avoid git tag -f and git push --tags -f, which can move existing release tags.

Safer shape
-      - name: Publish to PyPI
+      - name: Build distribution
         if: inputs.dry_run != true
         env:
           UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN || secrets.PYPI_API_TOKEN }}
         run: |
           uv lock
           uv build
-          uv publish
...
-          git commit -m "Release ${TAG}" || exit 0
-          git tag -f "${TAG}"
-          git pull --rebase origin main
-          git push origin main
-          git push --tags -f
+          git diff --quiet -- pyproject.toml uv.lock && {
+            echo "No release metadata changes to commit"
+            exit 1
+          }
+          git commit -m "Release ${TAG}"
+          git tag "${TAG}"
+          git push origin HEAD:main
+          git push origin "${TAG}"
           gh release create "${TAG}" --title "praisonai-plugins ${TAG}" --notes "Release ${TAG}" --latest
+
+      - name: Publish to PyPI
+        if: inputs.dry_run != true
+        env:
+          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN || secrets.PYPI_API_TOKEN }}
+        run: uv publish
🧰 Tools
🪛 zizmor (1.26.1)

[info] 121-121: prefer trusted publishing for authentication (use-trusted-publishing): this command

(use-trusted-publishing)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pypi-release.yml around lines 114 - 135, The release flow
in the Publish to PyPI and Commit, tag, and release steps should push the
versioned VCS state before running uv publish. Reorder the workflow so the
commit, tag, and pushes happen first, then publish to PyPI only after the git
tag and GitHub release are safely created, using the VERSION/TAG logic already
in place. Also remove force-moving tag operations in the release step by
avoiding git tag -f and git push --tags -f, and use non-forced tag creation/push
so release tags remain immutable.

Comment on lines +14 to +21
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run sync-secrets-to-aiui.yml \
--repo MervinPraison/PraisonAI \
--ref main \
-f target_repo=MervinPraison/PraisonAI-Plugins
echo "Triggered secret sync → MervinPraison/PraisonAI-Plugins"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

GITHUB_TOKEN cannot trigger workflows in a different repository.

The default secrets.GITHUB_TOKEN is scoped to MervinPraison/PraisonAI-Plugins only. The gh workflow run --repo MervinPraison/PraisonAI command requires a token with access to that repository. This workflow will fail with a permissions error.

Use a PAT or GitHub App token stored as a repository secret:

🔧 Proposed fix
       - name: Trigger PraisonAI secret sync
         env:
-          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GH_TOKEN: ${{ secrets.PRAISONAI_PAT }}
         run: |
           gh workflow run sync-secrets-to-aiui.yml \
             --repo MervinPraison/PraisonAI \
             --ref main \
             -f target_repo=MervinPraison/PraisonAI-Plugins
           echo "Triggered secret sync → MervinPraison/PraisonAI-Plugins"

Ensure the PAT has repo and workflow scopes for MervinPraison/PraisonAI.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run sync-secrets-to-aiui.yml \
--repo MervinPraison/PraisonAI \
--ref main \
-f target_repo=MervinPraison/PraisonAI-Plugins
echo "Triggered secret sync → MervinPraison/PraisonAI-Plugins"
env:
GH_TOKEN: ${{ secrets.PRAISONAI_PAT }}
run: |
gh workflow run sync-secrets-to-aiui.yml \
--repo MervinPraison/PraisonAI \
--ref main \
-f target_repo=MervinPraison/PraisonAI-Plugins
echo "Triggered secret sync → MervinPraison/PraisonAI-Plugins"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/sync-secrets-from-praisonai.yml around lines 14 - 21, The
sync-secrets workflow is using secrets.GITHUB_TOKEN to call gh workflow run
against MervinPraison/PraisonAI, but that token cannot trigger workflows in
another repository. Update the job in sync-secrets-from-praisonai.yml to use a
repository secret containing a PAT or GitHub App token with access to
MervinPraison/PraisonAI, and make sure the token has the required repo and
workflow scopes before invoking gh workflow run.

@MervinPraison MervinPraison merged commit 4b68d4b into main Jul 8, 2026
65 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant