fix: auto-trigger conflict and CI fixes without FINAL review gate

Remove the FINAL @claude prerequisite from merge-conflict and CI-failure
workflows, clear pipeline/blocked:conflict after rebase completion, add
bot-pr-recovery cron, and update gate selftests.
This commit is contained in:
MervinPraison committed 2026-07-29 18:15:42 +01:00
1 parent d008b6a250
commit 0719c41399
6 files changed
+56 -21

No files matched your search

@@ -32,4 +32,13 @@ const comment = ciFix.buildCiFixComment({
});
assert('comment mentions product guardrails', comment.includes('Product guardrails'));
const skip = ciFix.shouldSkipCiFix({
comments: [],
headSha: 'abc1234567890abcdef1234567890abcdef12',
labels: [],
claudeInProgress: false,
failedChecks: [{ name: 'python', workflow: 'CI' }],
});
assert('ci fix without FINAL review allowed', !skip.skip);
process.exit(failed ? 1 : 0);
+1 -4
View File
@@ -46,10 +46,9 @@ function hasRecentCiFixComment(comments, headSha) {
});
}
function shouldSkipCiFix({ comments, headSha, labels, hasFinal, claudeInProgress, failedChecks }) {
function shouldSkipCiFix({ comments, headSha, labels, claudeInProgress, failedChecks }) {
if (!failedChecks.length) return { skip: true, reason: 'no failed checks' };
if (labels.includes(CI_FIX_LABEL)) return { skip: true, reason: 'ci fix pending' };
if (!hasFinal) return { skip: true, reason: 'awaiting final claude review trigger' };
if (claudeInProgress) return { skip: true, reason: 'claude in progress' };
if (hasCiFixCommentForSha(comments, headSha)) {
return { skip: true, reason: 'already commented for this sha' };
@@ -221,7 +220,6 @@ async function maybeTriggerCiFixClaude(github, owner, repo, prNumber, core, opts
return { skipped: true, reason: 'no failed checks yet (may be pending)' };
}
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(ctx.comments);
const claudeInProgress = await mergeGate.hasInProgressClaudeAssistant(
github, owner, repo, prNumber
);
@@ -229,7 +227,6 @@ async function maybeTriggerCiFixClaude(github, owner, repo, prNumber, core, opts
comments: ctx.comments,
headSha,
labels,
hasFinal,
claudeInProgress,
failedChecks: failedRuns,
});
+1 -1
View File
@@ -205,7 +205,7 @@ const headAfterRebase = conflictIso(-21 * 60 * 1000);
const rebaseComments = [conflictTrigger, rebaseDone, finalAfterRebase];
assert('conflict blocks before rebase done', mg.hasRecentConflictComment([conflictTrigger], headAfterRebase));
assert('conflict clears after rebase + FINAL on HEAD', !mg.hasRecentConflictComment(rebaseComments, headAfterRebase));
assert('conflict still blocks without FINAL on HEAD', mg.hasRecentConflictComment(
assert('conflict clears after rebase without FINAL on HEAD', !mg.hasRecentConflictComment(
[conflictTrigger, rebaseDone],
headAfterRebase
));
+1 -1
View File
@@ -113,7 +113,7 @@ function conflictRebaseQuiescent(comments, headPushedAt) {
);
if (!rebaseDone) return false;
return finalClaudeCompletedOnSha(comments, headPushedAt);
return true;
}
function hasRecentConflictComment(comments, headPushedAt = null) {
+43
View File
@@ -0,0 +1,43 @@
name: Bot PR Recovery
# Belt-and-braces: kick CodeRabbit/Qodo when pull_request:opened never fired for bot PRs.
on:
schedule:
- cron: '*/15 * * * *'
workflow_dispatch:
inputs:
pr_number:
description: 'Specific PR number to kick (optional)'
required: false
type: string
permissions:
pull-requests: write
issues: write
jobs:
recover:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Find and kick stalled bot PRs
uses: actions/github-script@v7
env:
PR_NUMBER_INPUT: ${{ github.event.inputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const manual = (process.env.PR_NUMBER_INPUT || '').trim();
const prNumber = manual ? parseInt(manual, 10) : null;
if (manual && Number.isNaN(prNumber)) {
throw new Error(`Invalid pr_number: ${manual}`);
}
await chain.recoverStalledBotPrs(github, owner, repo, { prNumber }, core);
+1 -15
View File
@@ -12,7 +12,7 @@ on:
- cron: '0 */6 * * *'
concurrency:
group: merge-conflict-scan-${{ github.repository }}
group: merge-conflict-scan-${{ github.repository }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
env:
@@ -83,16 +83,6 @@ jobs:
};
}
function hasFinalClaudeReviewTrigger(comments) {
return comments.some((c) => {
const body = (c.body || '').toLowerCase();
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!body.includes('@claude')) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
}
function hasRecentAutoComment(comments) {
const cutoff = Date.now() - COOLDOWN_MS;
return comments.some((c) => {
@@ -143,10 +133,6 @@ jobs:
return { skipped: true, reason: 'recent auto comment' };
}
if (!hasFinalClaudeReviewTrigger(comments)) {
return { skipped: true, reason: 'awaiting final claude review trigger' };
}
await github.rest.issues.addLabels({
owner,
repo,