From a3229ea65701ae1f352769b0bf6d4e97a18aa7da Mon Sep 17 00:00:00 2001 From: Lev Demidov Date: Fri, 24 Jul 2026 10:18:45 -0400 Subject: [PATCH] fix: skip workflow files deleted within a PR getFilesInCommit collects files added/modified in each commit of a PR. A workflow file that is added or modified in one commit and then deleted in a later commit of the same PR is still collected from the earlier commit, but the checkout is the PR head where the file no longer exists. Reading it throws ENOENT and fails the whole run: ##[error]Unable to parse workflow file '.github/workflows/foo.yml' Error: ENOENT: no such file or directory, open '.github/workflows/foo.yml' Skip any workflow path not present on disk (deleted at head) and log it, instead of reading it. A workflow that no longer exists cannot violate the policy, and this also keeps such phantom files out of the evaluation output. lib/index.js is hand-patched to mirror src/main.ts exactly: a clean npm run build currently fails on the pinned @actions/* deps (ncc 0.38.4 cannot resolve their exports field), so the bundle could not be regenerated normally. Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/index.js | 46 ++++++++++++++++++++++++++------------------ src/main.ts | 54 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 58 insertions(+), 42 deletions(-) diff --git a/lib/index.js b/lib/index.js index 4ece3544..fff94cf0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -67632,29 +67632,37 @@ function run(context) { console.log("\nREADING WORKFLOW FILES"); workflowFilePaths.forEach((wf) => { console.log(line); + // A file can be added/modified in one commit of a PR yet deleted by the + // head (e.g. added then removed across commits). getFilesInCommit reports + // it as added/modified from the earlier commit, but the checkout is the + // head ref, so the file is not on disk. Skip it rather than reading it, + // which would throw ENOENT and fail the whole run for a file that no + // longer exists. A workflow deleted outright cannot violate the policy. + if (!fs_1.default.existsSync(wf)) { + console.log("\nSkipping (deleted in this change set): " + wf); + return; + } let workflow = { filePath: wf, actions: Array() }; workflowFiles.push(workflow); - if (fs_1.default.existsSync(workflow.filePath)) { - console.log("\nReading:" + workflow.filePath); - try { - let yaml = js_yaml_1.default.load(fs_1.default.readFileSync(workflow.filePath, "utf-8")); - let actionStrings = getPropertyValues(yaml, "uses"); - actionStrings.forEach((as) => { - workflow.actions.push(new Action(as)); - }); + console.log("\nReading:" + workflow.filePath); + try { + let yaml = js_yaml_1.default.load(fs_1.default.readFileSync(workflow.filePath, "utf-8")); + let actionStrings = getPropertyValues(yaml, "uses"); + actionStrings.forEach((as) => { + workflow.actions.push(new Action(as)); + }); + } + catch (error) { + if (error instanceof Error) { + core.debug(error.message); + core.setFailed(`Unable to parse workflow file '${workflow.filePath}' - please ensure it's formatted properly.`); } - catch (error) { - if (error instanceof Error) { - core.debug(error.message); - core.setFailed(`Unable to parse workflow file '${workflow.filePath}' - please ensure it's formatted properly.`); - } - else { - // Handle cases where error is not an Error object - core.debug("An unknown error occurred."); - core.setFailed("An unknown error occurred."); - } - console.log(error); + else { + // Handle cases where error is not an Error object + core.debug("An unknown error occurred."); + core.setFailed("An unknown error occurred."); } + console.log(error); } }); //iterate through all the workflow files found diff --git a/src/main.ts b/src/main.ts index d66b8a43..eef9f899 100644 --- a/src/main.ts +++ b/src/main.ts @@ -207,33 +207,41 @@ async function run(context: typeof github.context): Promise { console.log("\nREADING WORKFLOW FILES"); workflowFilePaths.forEach((wf) => { console.log(line); + // A file can be added/modified in one commit of a PR yet deleted by the + // head (e.g. added then removed across commits). getFilesInCommit reports + // it as added/modified from the earlier commit, but the checkout is the + // head ref, so the file is not on disk. Skip it rather than reading it, + // which would throw ENOENT and fail the whole run for a file that no + // longer exists. A workflow deleted outright cannot violate the policy. + if (!fs.existsSync(wf)) { + console.log("\nSkipping (deleted in this change set): " + wf); + return; + } let workflow: Workflow = { filePath: wf, actions: Array() }; workflowFiles.push(workflow); - if (fs.existsSync(workflow.filePath)) { - console.log("\nReading:" + workflow.filePath); - try { - let yaml: any = yamlParse.load( - fs.readFileSync(workflow.filePath, "utf-8"), + console.log("\nReading:" + workflow.filePath); + try { + let yaml: any = yamlParse.load( + fs.readFileSync(workflow.filePath, "utf-8"), + ); + let actionStrings = getPropertyValues(yaml, "uses"); + + actionStrings.forEach((as) => { + workflow.actions.push(new Action(as)); + }); + } catch (error: unknown) { + if (error instanceof Error) { + core.debug(error.message); + core.setFailed( + `Unable to parse workflow file '${workflow.filePath}' - please ensure it's formatted properly.`, ); - let actionStrings = getPropertyValues(yaml, "uses"); - - actionStrings.forEach((as) => { - workflow.actions.push(new Action(as)); - }); - } catch (error: unknown) { - if (error instanceof Error) { - core.debug(error.message); - core.setFailed( - `Unable to parse workflow file '${workflow.filePath}' - please ensure it's formatted properly.`, - ); - } else { - // Handle cases where error is not an Error object - core.debug("An unknown error occurred."); - core.setFailed("An unknown error occurred."); - } - console.log(error); + } else { + // Handle cases where error is not an Error object + core.debug("An unknown error occurred."); + core.setFailed("An unknown error occurred."); } - } + console.log(error); + } }); //iterate through all the workflow files found