From 6df3580bd3ed97cf0d63fb6382568f2238484b87 Mon Sep 17 00:00:00 2001 From: Ashley Mensah Date: Tue, 28 Apr 2026 15:55:12 +0200 Subject: [PATCH] fix(ci): handle project API permission errors gracefully GITHUB_TOKEN cannot access org-level Projects V2. Make addToProject return null on failure instead of crashing, and skip setTextField calls when project access is unavailable. A PAT with project scope is needed for full project board integration. --- .../scripts/apply-decisions.mjs | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/.github/issue-resolution/scripts/apply-decisions.mjs b/.github/issue-resolution/scripts/apply-decisions.mjs index 462bb3577..1500473f5 100644 --- a/.github/issue-resolution/scripts/apply-decisions.mjs +++ b/.github/issue-resolution/scripts/apply-decisions.mjs @@ -69,12 +69,16 @@ async function addToProject(issueNodeId) { } `; - const data = await graphql(mutation, { - projectId: process.env.PROJECT_ID, - contentId: issueNodeId - }); - - return data.addProjectV2ItemById.item.id; + try { + const data = await graphql(mutation, { + projectId: process.env.PROJECT_ID, + contentId: issueNodeId + }); + return data.addProjectV2ItemById.item.id; + } catch (err) { + console.warn(`[WARN] Could not add to project (needs PAT with project scope): ${err.message}`); + return null; + } } async function setTextField(itemId, fieldId, value) { @@ -107,8 +111,10 @@ for (const d of decisions) { await addLabel(owner, repo, d.issue_number, ["resolution-candidate"]); const issueNodeId = await getIssueNodeId(owner, repo, d.issue_number); const itemId = await addToProject(issueNodeId); - await setTextField(itemId, process.env.PROJECT_REASON_FIELD_ID, `DRY_RUN:${d.model.reason_code}`); - await setTextField(itemId, process.env.PROJECT_CONFIDENCE_FIELD_ID, String(d.model.confidence)); + if (itemId) { + await setTextField(itemId, process.env.PROJECT_REASON_FIELD_ID, `DRY_RUN:${d.model.reason_code}`); + await setTextField(itemId, process.env.PROJECT_CONFIDENCE_FIELD_ID, String(d.model.confidence)); + } console.log(`[DRY RUN] Would auto-close #${d.issue_number}`); continue; } @@ -124,23 +130,25 @@ for (const d of decisions) { const issueNodeId = await getIssueNodeId(owner, repo, d.issue_number); const itemId = await addToProject(issueNodeId); - if (process.env.PROJECT_CONFIDENCE_FIELD_ID) { - await setTextField(itemId, process.env.PROJECT_CONFIDENCE_FIELD_ID, String(d.model.confidence)); - } - if (process.env.PROJECT_REASON_FIELD_ID) { - await setTextField(itemId, process.env.PROJECT_REASON_FIELD_ID, d.model.reason_code); - } - if (process.env.PROJECT_EVIDENCE_FIELD_ID) { - await setTextField(itemId, process.env.PROJECT_EVIDENCE_FIELD_ID, d.issue_url); - } - if (process.env.PROJECT_LINKED_PR_FIELD_ID) { - const linked = (d.model.hard_signals || []).map(x => x.url).join(", "); - if (linked) { - await setTextField(itemId, process.env.PROJECT_LINKED_PR_FIELD_ID, linked); + if (itemId) { + if (process.env.PROJECT_CONFIDENCE_FIELD_ID) { + await setTextField(itemId, process.env.PROJECT_CONFIDENCE_FIELD_ID, String(d.model.confidence)); + } + if (process.env.PROJECT_REASON_FIELD_ID) { + await setTextField(itemId, process.env.PROJECT_REASON_FIELD_ID, d.model.reason_code); + } + if (process.env.PROJECT_EVIDENCE_FIELD_ID) { + await setTextField(itemId, process.env.PROJECT_EVIDENCE_FIELD_ID, d.issue_url); + } + if (process.env.PROJECT_LINKED_PR_FIELD_ID) { + const linked = (d.model.hard_signals || []).map(x => x.url).join(", "); + if (linked) { + await setTextField(itemId, process.env.PROJECT_LINKED_PR_FIELD_ID, linked); + } + } + if (process.env.PROJECT_REPO_FIELD_ID) { + await setTextField(itemId, process.env.PROJECT_REPO_FIELD_ID, d.repository); } - } - if (process.env.PROJECT_REPO_FIELD_ID) { - await setTextField(itemId, process.env.PROJECT_REPO_FIELD_ID, d.repository); } await addComment(