However, I’m encountering an issue during the merge process. Even when an auto-assigned reviewer approves the pull request, I'm still unable to merge the code into the main
branch
forge logs:
<redacted>
code for the reference:
index.js:
import api, { route } from '@forge/api';
// ==== CONFIGURATION ====
// From Forge logs: REQUIRED_REVIEWERS
const REQUIRED_REVIEWERS = [
'<redacted>',
'<redacted>'
];
// ==== HANDLERS ====
// Trigger: Assign reviewers when PR is created
export async function prtrigger(event) {
console.log("🔍 Incoming PR event payload:", JSON.stringify(event, null, 2));
try {
const workspaceUuid = event.workspace.uuid;
const repoUuid = event.repository.uuid;
const prId = event.pullrequest.id;
const prAuthor = await getPRAuthor(workspaceUuid, repoUuid, prId);
const reviewersToAssign = REQUIRED_REVIEWERS.filter(id => id !== prAuthor);
await assignPR(workspaceUuid, repoUuid, prId, reviewersToAssign);
await commentOnPR(workspaceUuid, repoUuid, prId, reviewersToAssign);
} catch (error) {
console.error('Error in prtrigger:', error.stack || error.message);
}
}
// Trigger: Re-add required reviewers if removed
export async function ensureReviewers(event) {
try {
const workspaceUuid = event.workspace.uuid;
const repoUuid = event.repository.uuid;
const prId = event.pullrequest.id;
const res = await api.asApp().requestBitbucket(
route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}`
);
const data = await res.json();
const currentReviewers = data.reviewers.map(r => r.account_id);
const missing = REQUIRED_REVIEWERS.filter(id => !currentReviewers.includes(id));
if (missing.length > 0) {
const updatedReviewers = [...new Set([...currentReviewers, ...missing])];
await assignPR(workspaceUuid, repoUuid, prId, updatedReviewers);
console.log(`➕ Re-added missing reviewers: ${missing}`);
}
} catch (error) {
console.error('❌ Error in ensureReviewers:', error.stack || error.message);
}
}
// Merge Check: Pass if at least one required reviewer approved
export async function mergeCheckHandler(event) {
try {
console.log("🔍 Incoming merge check event:", JSON.stringify(event, null, 2));
const workspaceUuid = event.workspace.uuid;
const repoUuid = event.repository.uuid;
const prId = event.pullrequest.id;
console.log(`📌 Workspace: ${workspaceUuid}, Repo: ${repoUuid}, PR: ${prId}`);
console.log("📌 REQUIRED_REVIEWERS:", REQUIRED_REVIEWERS);
// Step 1: Fetch PR details
const prRes = await api.asApp().requestBitbucket(
route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}`
);
console.log(`📡 PR details status: ${prRes.status}`);
const prData = await prRes.json();
console.log("📜 PR details:", JSON.stringify(prData, null, 2));
const currentReviewers = prData.reviewers.map(r => r.account_id);
console.log("👥 Assigned reviewers:", currentReviewers);
// Step 2: Ensure all required reviewers are assigned
const missingReviewers = REQUIRED_REVIEWERS.filter(id => !currentReviewers.includes(id));
if (missingReviewers.length > 0) {
console.log("⚠️ Missing reviewers:", missingReviewers);
return {
status: 'FAILED',
message: `❌ Missing required reviewers: ${missingReviewers.join(', ')}`
};
}
// Step 3: Fetch PR activity for approvals
const activityRes = await api.asApp().requestBitbucket(
route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}/activity`
);
console.log(`📡 PR activity status: ${activityRes.status}`);
const activityData = await activityRes.json();
console.log("📜 Activity raw:", JSON.stringify(activityData, null, 2));
const approvals = (activityData.values || [])
.filter(item => item.approval?.user?.account_id)
.map(item => item.approval.user.account_id);
console.log("✅ Approved reviewer IDs:", approvals);
// Step 4: Pass if at least one required reviewer approved
const hasApprovalFromRequired = REQUIRED_REVIEWERS.some(id => approvals.includes(id));
console.log(`🔍 Approval from at least one required reviewer: ${hasApprovalFromRequired}`);
if (!hasApprovalFromRequired) {
return {
status: 'FAILED',
message: `❌ Need approval from at least one required reviewer. Approved IDs: ${approvals.join(', ')}`
};
}
// Step 5: Passed
console.log("🎉 Merge check passed");
return { status: 'PASSED' };
} catch (error) {
console.error('❌ Error in mergeCheckHandler:', error.stack || error.message);
return {
status: 'FAILED',
message: '⚠️ Error validating reviewers. Check Forge logs.'
};
}
}
// ==== HELPERS ====
const getPRAuthor = async (workspaceUuid, repoUuid, prId) => {
const res = await api.asApp().requestBitbucket(
route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}`
);
const data = await res.json();
return data.author.account_id;
};
const assignPR = async (workspaceUuid, repoUuid, prId, reviewers) => {
const body = { reviewers: reviewers.map(id => ({ account_id: id })) };
const res = await api.asApp().requestBitbucket(
route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}`,
{
method: 'PUT',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify(body)
}
);
return await res.json();
};
const commentOnPR = async (workspaceUuid, repoUuid, prId, reviewers) => {
const body = {
content: { raw: `The PR has been automatically assigned to: ${reviewers.map(id => `@{${id}}`).join(', ')}` }
};
await api.asApp().requestBitbucket(
route`/2.0/repositories/${workspaceUuid}/${repoUuid}/pullrequests/${prId}/comments`,
{
method: 'POST',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
body: JSON.stringify(body)
}
);
};
manifest.yml
modules:
trigger:
- key: pullrequest-created-event
function: main
events:
- avi:bitbucket:created:pullrequest
- key: pullrequest-updated-event
function: ensureReviewers
events:
- avi:bitbucket:updated:pullrequest
bitbucket:mergeCheck:
- key: enforce-reviewers-check
function: mergeCheckHandler
name: Enforce Required Reviewers
description: Ensures required reviewers are present and at least one approves before merging
triggers:
- on-merge
function:
- key: main
handler: index.prtrigger
- key: ensureReviewers
handler: index.ensureReviewers
- key: mergeCheckHandler
handler: index.mergeCheckHandler
permissions:
scopes:
- read:pullrequest:bitbucket
- write:pullrequest:bitbucket
- read:repository:bitbucket
- read:workspace:bitbucket
app:
<redacted>
runtime:
name: nodejs22.x
issue: Even after a reviewer approves, the merge is still being blocked
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.