From 0bd352155584db50b6f112978648904d7506a982 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Sep 2025 13:52:47 +0000
Subject: [PATCH 1/3] Initial plan
From 950ff294ddf0adda84144c2282c8b2296984c18c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Sep 2025 14:08:23 +0000
Subject: [PATCH 2/3] Implement custom setCancelled function with
self-cancellation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
...est-safe-output-add-issue-comment.lock.yml | 30 +++++++--
.../test-safe-output-add-issue-label.lock.yml | 30 +++++++--
...output-create-code-scanning-alert.lock.yml | 30 +++++++--
...est-safe-output-create-discussion.lock.yml | 30 +++++++--
.../test-safe-output-create-issue.lock.yml | 55 ++++++++++++++--
...reate-pull-request-review-comment.lock.yml | 30 +++++++--
...t-safe-output-create-pull-request.lock.yml | 30 +++++++--
...est-safe-output-push-to-pr-branch.lock.yml | 30 +++++++--
.../test-safe-output-update-issue.lock.yml | 30 +++++++--
pkg/workflow/compiler.go | 63 ++++++++++++++++--
pkg/workflow/js.go | 3 +
pkg/workflow/js/check_permissions.cjs | 26 +++++++-
pkg/workflow/js/check_permissions.test.cjs | 5 ++
pkg/workflow/js/check_team_member.cjs | 29 +++++++++
pkg/workflow/js/check_team_member.test.cjs | 64 ++++++++++++++++++-
15 files changed, 437 insertions(+), 48 deletions(-)
diff --git a/.github/workflows/test-safe-output-add-issue-comment.lock.yml b/.github/workflows/test-safe-output-add-issue-comment.lock.yml
index c907e1ad540..c5b66f2d063 100644
--- a/.github/workflows/test-safe-output-add-issue-comment.lock.yml
+++ b/.github/workflows/test-safe-output-add-issue-comment.lock.yml
@@ -23,7 +23,11 @@ run-name: "Test Safe Output - Add Issue Comment"
jobs:
test-safe-output-add-issue-comment:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -34,6 +38,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -52,7 +73,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -88,16 +109,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/.github/workflows/test-safe-output-add-issue-label.lock.yml b/.github/workflows/test-safe-output-add-issue-label.lock.yml
index de44b3b7577..35fd5beca22 100644
--- a/.github/workflows/test-safe-output-add-issue-label.lock.yml
+++ b/.github/workflows/test-safe-output-add-issue-label.lock.yml
@@ -25,7 +25,11 @@ run-name: "Test Safe Output - Add Issue Label"
jobs:
test-safe-output-add-issue-label:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -36,6 +40,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -54,7 +75,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -90,16 +111,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml
index 32568d9cde1..1ca1b3290ae 100644
--- a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml
+++ b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml
@@ -27,7 +27,11 @@ run-name: "Test Safe Output - Create Code Scanning Alert"
jobs:
test-safe-output-create-code-scanning-alert:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -38,6 +42,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -56,7 +77,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -92,16 +113,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/.github/workflows/test-safe-output-create-discussion.lock.yml b/.github/workflows/test-safe-output-create-discussion.lock.yml
index da147359f55..5705b159e79 100644
--- a/.github/workflows/test-safe-output-create-discussion.lock.yml
+++ b/.github/workflows/test-safe-output-create-discussion.lock.yml
@@ -22,7 +22,11 @@ run-name: "Test Safe Output - Create Discussion"
jobs:
test-safe-output-create-discussion:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -33,6 +37,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -51,7 +72,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -87,16 +108,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/.github/workflows/test-safe-output-create-issue.lock.yml b/.github/workflows/test-safe-output-create-issue.lock.yml
index a3fa7a5eaef..5ab4d6a708a 100644
--- a/.github/workflows/test-safe-output-create-issue.lock.yml
+++ b/.github/workflows/test-safe-output-create-issue.lock.yml
@@ -19,7 +19,11 @@ run-name: "Test Safe Output - Create Issue"
jobs:
test-safe-output-create-issue:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -30,6 +34,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -48,7 +69,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -84,16 +105,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
@@ -1096,6 +1118,7 @@ jobs:
needs: test-safe-output-create-issue
runs-on: ubuntu-latest
permissions:
+ actions: write
contents: read
issues: write
timeout-minutes: 10
@@ -1110,6 +1133,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -1128,7 +1168,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -1164,16 +1204,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Create Output Issue
diff --git a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml
index 2bf4e7d95aa..a06cd9204aa 100644
--- a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml
+++ b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml
@@ -21,7 +21,11 @@ run-name: "Test Safe Output - Create Pull Request Review Comment"
jobs:
test-safe-output-create-pull-request-review-comment:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -32,6 +36,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -50,7 +71,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -86,16 +107,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/.github/workflows/test-safe-output-create-pull-request.lock.yml b/.github/workflows/test-safe-output-create-pull-request.lock.yml
index 1b5832803c7..71d4c8a7526 100644
--- a/.github/workflows/test-safe-output-create-pull-request.lock.yml
+++ b/.github/workflows/test-safe-output-create-pull-request.lock.yml
@@ -20,7 +20,11 @@ run-name: "Test Safe Output - Create Pull Request"
jobs:
test-safe-output-create-pull-request:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -31,6 +35,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -49,7 +70,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -85,16 +106,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml
index 36d3c29b7dc..9c69dc28a50 100644
--- a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml
+++ b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml
@@ -21,7 +21,11 @@ run-name: "Test Safe Output - Push to PR Branch"
jobs:
test-safe-output-push-to-pr-branch:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -32,6 +36,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -50,7 +71,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -86,16 +107,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/.github/workflows/test-safe-output-update-issue.lock.yml b/.github/workflows/test-safe-output-update-issue.lock.yml
index 8a12be65c00..e21428d3215 100644
--- a/.github/workflows/test-safe-output-update-issue.lock.yml
+++ b/.github/workflows/test-safe-output-update-issue.lock.yml
@@ -20,7 +20,11 @@ run-name: "Test Safe Output - Update Issue"
jobs:
test-safe-output-update-issue:
runs-on: ubuntu-latest
- permissions: read-all
+ permissions:
+ actions: write
+ contents: read
+ issues: read
+ pull-requests: read
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
@@ -31,6 +35,23 @@ jobs:
GITHUB_AW_REQUIRED_ROLES: admin,maintainer
with:
script: |
+ // Custom setCancelled function that uses self-cancellation
+ async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+ }
async function main() {
const { eventName } = context;
// skip check for safe events
@@ -49,7 +70,7 @@ jobs:
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -85,16 +106,17 @@ jobs:
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
// Cancel the job when permission check fails
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
- name: Checkout repository
diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go
index 5a0d253b6e6..07de406b4c5 100644
--- a/pkg/workflow/compiler.go
+++ b/pkg/workflow/compiler.go
@@ -2018,11 +2018,18 @@ func (c *Compiler) buildTaskJob(data *WorkflowData, frontmatter map[string]any)
steps = append(steps, " run: echo \"Task job executed - conditions satisfied\"\n")
}
+ // Determine permissions needed for task job
+ var permissions string
+ if needsPermissionCheck || data.Command != "" {
+ // Add actions: write permission for self-cancellation when team member checks are present
+ permissions = "permissions:\n actions: write"
+ }
+
job := &Job{
Name: "task",
If: data.If, // Use the existing condition (which may include alias checks)
RunsOn: "runs-on: ubuntu-latest",
- Permissions: "", // No permissions needed - task job does not require content access
+ Permissions: permissions,
Steps: steps,
Outputs: outputs,
}
@@ -2039,7 +2046,7 @@ core.setOutput("is_team_member", "true");
console.log("Permission check skipped - 'roles: all' specified");`
}
- // Use the embedded check_permissions.cjs script
+ // Use the embedded check_permissions.cjs script for flexible permission checking
// The GITHUB_AW_REQUIRED_ROLES environment variable is set via the env field
return checkPermissionsScript
}
@@ -2101,11 +2108,17 @@ func (c *Compiler) buildAddReactionJob(data *WorkflowData, taskJobCreated bool,
depends = []string{"task"} // Depend on the task job only if it exists
}
+ // Determine permissions - add actions: write if permission checks are added
+ permissions := "permissions:\n issues: write\n pull-requests: write"
+ if !taskJobCreated && c.needsPermissionChecks(data) {
+ permissions = "permissions:\n actions: write\n issues: write\n pull-requests: write"
+ }
+
job := &Job{
Name: "add_reaction",
If: reactionCondition.Render(),
RunsOn: "runs-on: ubuntu-latest",
- Permissions: "permissions:\n issues: write\n pull-requests: write",
+ Permissions: permissions,
Steps: steps,
Outputs: outputs,
Needs: depends,
@@ -2186,11 +2199,17 @@ func (c *Compiler) buildCreateOutputIssueJob(data *WorkflowData, mainJobName str
jobCondition = "" // No conditional execution
}
+ // Determine permissions - add actions: write if permission checks are added
+ permissions := "permissions:\n contents: read\n issues: write"
+ if !taskJobCreated && c.needsPermissionChecks(data) {
+ permissions = "permissions:\n actions: write\n contents: read\n issues: write"
+ }
+
job := &Job{
Name: "create_issue",
If: jobCondition,
RunsOn: "runs-on: ubuntu-latest",
- Permissions: "permissions:\n contents: read\n issues: write",
+ Permissions: permissions,
TimeoutMinutes: 10, // 10-minute timeout as required
Steps: steps,
Outputs: outputs,
@@ -2660,11 +2679,45 @@ func (c *Compiler) buildMainJob(data *WorkflowData, jobName string, taskJobCreat
jobCondition = data.If // Use the original If condition from the workflow data
}
+ // Determine permissions - modify permissions to include actions: write if permission checks are added
+ permissions := data.Permissions
+ if !taskJobCreated {
+ var needsPermissionCheck bool
+ // Check if permission checks are needed using frontmatter if available
+ if frontmatter != nil {
+ needsPermissionCheck = c.needsPermissionChecksWithFrontmatter(data, frontmatter)
+ } else {
+ needsPermissionCheck = c.needsPermissionChecks(data)
+ }
+
+ if needsPermissionCheck {
+ // Add actions: write permission for self-cancellation
+ if permissions == "" || permissions == "permissions: read-all" {
+ permissions = "permissions:\n actions: write\n contents: read\n issues: read\n pull-requests: read"
+ } else {
+ // Parse existing permissions and add actions: write if not present
+ if !strings.Contains(permissions, "actions:") {
+ // Insert actions: write at the beginning of the permissions block
+ lines := strings.Split(permissions, "\n")
+ if len(lines) > 0 && strings.Contains(lines[0], "permissions:") {
+ // Insert after the permissions: line
+ newLines := []string{lines[0], " actions: write"}
+ newLines = append(newLines, lines[1:]...)
+ permissions = strings.Join(newLines, "\n")
+ } else {
+ // Fallback: prepend to existing permissions
+ permissions = "permissions:\n actions: write\n" + strings.TrimPrefix(permissions, "permissions:\n")
+ }
+ }
+ }
+ }
+ }
+
job := &Job{
Name: jobName,
If: jobCondition,
RunsOn: c.indentYAMLLines(data.RunsOn, " "),
- Permissions: c.indentYAMLLines(data.Permissions, " "),
+ Permissions: c.indentYAMLLines(permissions, " "),
Steps: steps,
Needs: depends,
Outputs: outputs,
diff --git a/pkg/workflow/js.go b/pkg/workflow/js.go
index 758cf14cf9d..154ed7897f7 100644
--- a/pkg/workflow/js.go
+++ b/pkg/workflow/js.go
@@ -51,6 +51,9 @@ var addReactionAndEditCommentScript string
//go:embed js/check_permissions.cjs
var checkPermissionsScript string
+//go:embed js/check_team_member.cjs
+var checkTeamMemberScript string
+
//go:embed js/parse_claude_log.cjs
var parseClaudeLogScript string
diff --git a/pkg/workflow/js/check_permissions.cjs b/pkg/workflow/js/check_permissions.cjs
index e2ec20060bb..1b14376fe56 100644
--- a/pkg/workflow/js/check_permissions.cjs
+++ b/pkg/workflow/js/check_permissions.cjs
@@ -1,3 +1,22 @@
+// Custom setCancelled function that uses self-cancellation
+async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+}
+
async function main() {
const { eventName } = context;
@@ -19,7 +38,7 @@ async function main() {
core.error(
"❌ Configuration error: Required permissions not specified. Contact repository administrator."
);
- core.setCancelled(
+ await setCancelled(
"Configuration error: Required permissions not specified"
);
return;
@@ -60,7 +79,7 @@ async function main() {
const errorMessage =
repoError instanceof Error ? repoError.message : String(repoError);
core.error(`Repository permission check failed: ${errorMessage}`);
- core.setCancelled(`Repository permission check failed: ${errorMessage}`);
+ await setCancelled(`Repository permission check failed: ${errorMessage}`);
return;
}
@@ -68,8 +87,9 @@ async function main() {
core.warning(
`❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
- core.setCancelled(
+ await setCancelled(
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
);
+ return;
}
await main();
diff --git a/pkg/workflow/js/check_permissions.test.cjs b/pkg/workflow/js/check_permissions.test.cjs
index 4cfa98faf39..73916a72cce 100644
--- a/pkg/workflow/js/check_permissions.test.cjs
+++ b/pkg/workflow/js/check_permissions.test.cjs
@@ -10,6 +10,7 @@ const mockCore = {
setFailed: vi.fn(),
setCancelled: vi.fn(),
setError: vi.fn(),
+ info: vi.fn(),
};
const mockGithub = {
@@ -17,6 +18,9 @@ const mockGithub = {
repos: {
getCollaboratorPermissionLevel: vi.fn(),
},
+ actions: {
+ cancelWorkflowRun: vi.fn(),
+ },
},
};
@@ -27,6 +31,7 @@ const mockContext = {
owner: "testowner",
repo: "testrepo",
},
+ runId: 12345,
};
// Set up global variables
diff --git a/pkg/workflow/js/check_team_member.cjs b/pkg/workflow/js/check_team_member.cjs
index 3a342bae627..a84b52e8c53 100644
--- a/pkg/workflow/js/check_team_member.cjs
+++ b/pkg/workflow/js/check_team_member.cjs
@@ -1,3 +1,22 @@
+// Custom setCancelled function that uses self-cancellation
+async function setCancelled(message) {
+ try {
+ // Cancel the current workflow run using GitHub Actions API
+ await github.rest.actions.cancelWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: context.runId
+ });
+
+ core.info(`Cancellation requested for this workflow run: ${message}`);
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ core.warning(`Failed to cancel workflow run: ${errorMessage}`);
+ // Fallback to core.setCancelled if API call fails
+ core.setCancelled(message);
+ }
+}
+
async function main() {
const actor = context.actor;
const { owner, repo } = context.repo;
@@ -29,6 +48,16 @@ async function main() {
core.warning(`Repository permission check failed: ${errorMessage}`);
}
+ // Team membership check failed - use self-cancellation
+ const failureMessage = `Access denied: User '${actor}' is not authorized to trigger this workflow. Only admin or maintainer users can run this workflow.`;
+ core.warning(`❌ ${failureMessage}`);
+
+ await setCancelled(failureMessage);
+
+ // Set output for any dependent steps that might check before cancellation takes effect
core.setOutput("is_team_member", "false");
+
+ // Return to finish the script
+ return;
}
await main();
diff --git a/pkg/workflow/js/check_team_member.test.cjs b/pkg/workflow/js/check_team_member.test.cjs
index ad3a5db524f..2c3501aa031 100644
--- a/pkg/workflow/js/check_team_member.test.cjs
+++ b/pkg/workflow/js/check_team_member.test.cjs
@@ -7,6 +7,8 @@ const mockCore = {
setOutput: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
+ info: vi.fn(),
+ setCancelled: vi.fn(),
};
const mockGithub = {
@@ -14,6 +16,9 @@ const mockGithub = {
repos: {
getCollaboratorPermissionLevel: vi.fn(),
},
+ actions: {
+ cancelWorkflowRun: vi.fn(),
+ },
},
};
@@ -23,6 +28,7 @@ const mockContext = {
owner: "testowner",
repo: "testrepo",
},
+ runId: 12345,
};
// Set up global variables
@@ -145,11 +151,13 @@ describe("check_team_member.cjs", () => {
consoleSpy.mockRestore();
});
- it("should set is_team_member to false for read permission", async () => {
+ it("should use self-cancellation when team membership fails", async () => {
mockGithub.rest.repos.getCollaboratorPermissionLevel.mockResolvedValue({
data: { permission: "read" },
});
+ mockGithub.rest.actions.cancelWorkflowRun.mockResolvedValue({});
+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
// Execute the script
@@ -169,6 +177,60 @@ describe("check_team_member.cjs", () => {
expect(consoleSpy).toHaveBeenCalledWith(
"Repository permission level: read"
);
+
+ // Check that self-cancellation was attempted
+ expect(mockGithub.rest.actions.cancelWorkflowRun).toHaveBeenCalledWith({
+ owner: "testowner",
+ repo: "testrepo",
+ run_id: 12345,
+ });
+
+ expect(mockCore.info).toHaveBeenCalledWith(
+ expect.stringContaining("Cancellation requested for this workflow run")
+ );
+ expect(mockCore.warning).toHaveBeenCalledWith(
+ expect.stringContaining("Access denied: User 'testuser' is not authorized")
+ );
+ expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false");
+
+ consoleSpy.mockRestore();
+ });
+
+ it("should fallback to core.setCancelled when API call fails", async () => {
+ mockGithub.rest.repos.getCollaboratorPermissionLevel.mockResolvedValue({
+ data: { permission: "read" },
+ });
+
+ const apiError = new Error("API Error: Forbidden");
+ mockGithub.rest.actions.cancelWorkflowRun.mockRejectedValue(apiError);
+
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+
+ // Execute the script
+ await eval(`(async () => { ${checkTeamMemberScript} })()`);
+
+ expect(
+ mockGithub.rest.repos.getCollaboratorPermissionLevel
+ ).toHaveBeenCalledWith({
+ owner: "testowner",
+ repo: "testrepo",
+ username: "testuser",
+ });
+
+ // Check that self-cancellation was attempted but failed
+ expect(mockGithub.rest.actions.cancelWorkflowRun).toHaveBeenCalledWith({
+ owner: "testowner",
+ repo: "testrepo",
+ run_id: 12345,
+ });
+
+ // Check that fallback occurred
+ expect(mockCore.warning).toHaveBeenCalledWith(
+ "Failed to cancel workflow run: API Error: Forbidden"
+ );
+ expect(mockCore.setCancelled).toHaveBeenCalledWith(
+ expect.stringContaining("Access denied: User 'testuser' is not authorized")
+ );
expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false");
consoleSpy.mockRestore();
From 8b7d9e87b0f2c90f7a7830893898c709e7aaf72d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Sep 2025 14:36:38 +0000
Subject: [PATCH 3/3] Complete implementation of custom setCancelled function
with self-cancellation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
...est-safe-output-add-issue-comment.lock.yml | 97 +------
.../test-safe-output-add-issue-label.lock.yml | 97 +------
...output-create-code-scanning-alert.lock.yml | 97 +------
...est-safe-output-create-discussion.lock.yml | 97 +------
.../test-safe-output-create-issue.lock.yml | 189 +------------
...reate-pull-request-review-comment.lock.yml | 97 +------
...t-safe-output-create-pull-request.lock.yml | 97 +------
...est-safe-output-push-to-pr-branch.lock.yml | 97 +------
.../test-safe-output-update-issue.lock.yml | 97 +------
pkg/workflow/actions_write_permission_test.go | 259 ++++++++++++++++++
pkg/workflow/compiler.go | 85 +++---
pkg/workflow/compiler_test.go | 34 ++-
pkg/workflow/js.go | 3 -
pkg/workflow/js/check_permissions.cjs | 11 +-
pkg/workflow/js/check_team_member.cjs | 17 +-
pkg/workflow/js/check_team_member.test.cjs | 9 +-
pkg/workflow/js/compute_text.cjs | 2 -
pkg/workflow/js/compute_text.test.cjs | 9 +-
pkg/workflow/js/sanitize_output.cjs | 2 -
pkg/workflow/js/sanitize_output.test.cjs | 21 +-
pkg/workflow/output_test.go | 7 +-
21 files changed, 390 insertions(+), 1034 deletions(-)
create mode 100644 pkg/workflow/actions_write_permission_test.go
diff --git a/.github/workflows/test-safe-output-add-issue-comment.lock.yml b/.github/workflows/test-safe-output-add-issue-comment.lock.yml
index c5b66f2d063..16f01d096dd 100644
--- a/.github/workflows/test-safe-output-add-issue-comment.lock.yml
+++ b/.github/workflows/test-safe-output-add-issue-comment.lock.yml
@@ -23,105 +23,10 @@ run-name: "Test Safe Output - Add Issue Comment"
jobs:
test-safe-output-add-issue-comment:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup agent output
diff --git a/.github/workflows/test-safe-output-add-issue-label.lock.yml b/.github/workflows/test-safe-output-add-issue-label.lock.yml
index 35fd5beca22..d75b2e327fd 100644
--- a/.github/workflows/test-safe-output-add-issue-label.lock.yml
+++ b/.github/workflows/test-safe-output-add-issue-label.lock.yml
@@ -25,105 +25,10 @@ run-name: "Test Safe Output - Add Issue Label"
jobs:
test-safe-output-add-issue-label:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup agent output
diff --git a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml
index 1ca1b3290ae..497504f30c8 100644
--- a/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml
+++ b/.github/workflows/test-safe-output-create-code-scanning-alert.lock.yml
@@ -27,105 +27,10 @@ run-name: "Test Safe Output - Create Code Scanning Alert"
jobs:
test-safe-output-create-code-scanning-alert:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup agent output
diff --git a/.github/workflows/test-safe-output-create-discussion.lock.yml b/.github/workflows/test-safe-output-create-discussion.lock.yml
index 5705b159e79..895df572536 100644
--- a/.github/workflows/test-safe-output-create-discussion.lock.yml
+++ b/.github/workflows/test-safe-output-create-discussion.lock.yml
@@ -22,105 +22,10 @@ run-name: "Test Safe Output - Create Discussion"
jobs:
test-safe-output-create-discussion:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup agent output
diff --git a/.github/workflows/test-safe-output-create-issue.lock.yml b/.github/workflows/test-safe-output-create-issue.lock.yml
index 5ab4d6a708a..4ffcdca9c39 100644
--- a/.github/workflows/test-safe-output-create-issue.lock.yml
+++ b/.github/workflows/test-safe-output-create-issue.lock.yml
@@ -19,105 +19,10 @@ run-name: "Test Safe Output - Create Issue"
jobs:
test-safe-output-create-issue:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup agent output
@@ -1118,7 +1023,6 @@ jobs:
needs: test-safe-output-create-issue
runs-on: ubuntu-latest
permissions:
- actions: write
contents: read
issues: write
timeout-minutes: 10
@@ -1126,97 +1030,6 @@ jobs:
issue_number: ${{ steps.create_issue.outputs.issue_number }}
issue_url: ${{ steps.create_issue.outputs.issue_url }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Create Output Issue
id: create_issue
uses: actions/github-script@v7
diff --git a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml
index a06cd9204aa..485b3b7b802 100644
--- a/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml
+++ b/.github/workflows/test-safe-output-create-pull-request-review-comment.lock.yml
@@ -21,105 +21,10 @@ run-name: "Test Safe Output - Create Pull Request Review Comment"
jobs:
test-safe-output-create-pull-request-review-comment:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup agent output
diff --git a/.github/workflows/test-safe-output-create-pull-request.lock.yml b/.github/workflows/test-safe-output-create-pull-request.lock.yml
index 71d4c8a7526..002622b178c 100644
--- a/.github/workflows/test-safe-output-create-pull-request.lock.yml
+++ b/.github/workflows/test-safe-output-create-pull-request.lock.yml
@@ -20,105 +20,10 @@ run-name: "Test Safe Output - Create Pull Request"
jobs:
test-safe-output-create-pull-request:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Configure Git credentials
diff --git a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml
index 9c69dc28a50..8b6b7f89f67 100644
--- a/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml
+++ b/.github/workflows/test-safe-output-push-to-pr-branch.lock.yml
@@ -21,105 +21,10 @@ run-name: "Test Safe Output - Push to PR Branch"
jobs:
test-safe-output-push-to-pr-branch:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Configure Git credentials
diff --git a/.github/workflows/test-safe-output-update-issue.lock.yml b/.github/workflows/test-safe-output-update-issue.lock.yml
index e21428d3215..34d61157bb2 100644
--- a/.github/workflows/test-safe-output-update-issue.lock.yml
+++ b/.github/workflows/test-safe-output-update-issue.lock.yml
@@ -20,105 +20,10 @@ run-name: "Test Safe Output - Update Issue"
jobs:
test-safe-output-update-issue:
runs-on: ubuntu-latest
- permissions:
- actions: write
- contents: read
- issues: read
- pull-requests: read
+ permissions: read-all
outputs:
output: ${{ steps.collect_output.outputs.output }}
steps:
- - name: Check team membership for workflow
- id: check-team-member
- uses: actions/github-script@v7
- env:
- GITHUB_AW_REQUIRED_ROLES: admin,maintainer
- with:
- script: |
- // Custom setCancelled function that uses self-cancellation
- async function setCancelled(message) {
- try {
- // Cancel the current workflow run using GitHub Actions API
- await github.rest.actions.cancelWorkflowRun({
- owner: context.repo.owner,
- repo: context.repo.repo,
- run_id: context.runId
- });
- core.info(`Cancellation requested for this workflow run: ${message}`);
- } catch (error) {
- const errorMessage = error instanceof Error ? error.message : String(error);
- core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
- }
- }
- async function main() {
- const { eventName } = context;
- // skip check for safe events
- const safeEvents = ["workflow_dispatch", "workflow_run", "schedule"];
- if (safeEvents.includes(eventName)) {
- console.log(`✅ Event ${eventName} does not require validation`);
- return;
- }
- const actor = context.actor;
- const { owner, repo } = context.repo;
- const requiredPermissionsEnv = process.env.GITHUB_AW_REQUIRED_ROLES;
- const requiredPermissions = requiredPermissionsEnv
- ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "")
- : [];
- if (!requiredPermissions || requiredPermissions.length === 0) {
- core.error(
- "❌ Configuration error: Required permissions not specified. Contact repository administrator."
- );
- await setCancelled(
- "Configuration error: Required permissions not specified"
- );
- return;
- }
- // Check if the actor has the required repository permissions
- try {
- console.log(
- `Checking if user '${actor}' has required permissions for ${owner}/${repo}`
- );
- console.log(`Required permissions: ${requiredPermissions.join(", ")}`);
- const repoPermission =
- await github.rest.repos.getCollaboratorPermissionLevel({
- owner: owner,
- repo: repo,
- username: actor,
- });
- const permission = repoPermission.data.permission;
- console.log(`Repository permission level: ${permission}`);
- // Check if user has one of the required permission levels
- for (const requiredPerm of requiredPermissions) {
- if (
- permission === requiredPerm ||
- (requiredPerm === "maintainer" && permission === "maintain")
- ) {
- console.log(`✅ User has ${permission} access to repository`);
- return;
- }
- }
- console.log(
- `User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`
- );
- } catch (repoError) {
- const errorMessage =
- repoError instanceof Error ? repoError.message : String(repoError);
- core.error(`Repository permission check failed: ${errorMessage}`);
- await setCancelled(`Repository permission check failed: ${errorMessage}`);
- return;
- }
- // Cancel the job when permission check fails
- core.warning(
- `❌ Access denied: Only authorized users can trigger this workflow. User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- await setCancelled(
- `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
- );
- return;
- }
- await main();
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup agent output
diff --git a/pkg/workflow/actions_write_permission_test.go b/pkg/workflow/actions_write_permission_test.go
new file mode 100644
index 00000000000..dd91f8acd1f
--- /dev/null
+++ b/pkg/workflow/actions_write_permission_test.go
@@ -0,0 +1,259 @@
+package workflow
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+// TestActionsWritePermissionForSelfCancellation tests that actions: write permission
+// is added to jobs that include team member checks for self-cancellation functionality
+func TestActionsWritePermissionForSelfCancellation(t *testing.T) {
+ // Create temporary directory for test files
+ tmpDir, err := os.MkdirTemp("", "actions-write-permission-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ compiler := NewCompiler(false, "", "test")
+
+ tests := []struct {
+ name string
+ frontmatter string
+ filename string
+ expectActionsWrite bool
+ jobName string
+ description string
+ }{
+ {
+ name: "command workflow task job should have actions: write",
+ frontmatter: `---
+on:
+ command:
+ name: test-bot
+tools:
+ github:
+ allowed: [list_issues]
+---
+
+# Command Workflow
+Test workflow with command trigger.`,
+ filename: "command-workflow.md",
+ expectActionsWrite: true,
+ jobName: "task",
+ description: "Task job should have actions: write for self-cancellation",
+ },
+ {
+ name: "push workflow main job should have actions: write",
+ frontmatter: `---
+on:
+ push:
+ branches: [main]
+tools:
+ github:
+ allowed: [list_issues]
+---
+
+# Push Workflow
+Test workflow with push trigger that needs permission checks.`,
+ filename: "push-workflow.md",
+ expectActionsWrite: true,
+ jobName: "push-workflow",
+ description: "Main job should have actions: write for permission checks",
+ },
+ {
+ name: "workflow_dispatch should not have actions: write",
+ frontmatter: `---
+on:
+ workflow_dispatch:
+tools:
+ github:
+ allowed: [list_issues]
+---
+
+# Workflow Dispatch
+Test workflow with safe event.`,
+ filename: "workflow-dispatch.md",
+ expectActionsWrite: false,
+ jobName: "",
+ description: "Safe events should not need actions: write permission",
+ },
+ {
+ name: "schedule workflow should not have actions: write",
+ frontmatter: `---
+on:
+ schedule:
+ - cron: "0 9 * * 1"
+tools:
+ github:
+ allowed: [list_issues]
+---
+
+# Schedule Workflow
+Test workflow with schedule trigger.`,
+ filename: "schedule-workflow.md",
+ expectActionsWrite: false,
+ jobName: "",
+ description: "Schedule events should not need actions: write permission",
+ },
+ {
+ name: "roles: all should not have actions: write",
+ frontmatter: `---
+on:
+ push:
+ branches: [main]
+roles: all
+tools:
+ github:
+ allowed: [list_issues]
+---
+
+# Unrestricted Workflow
+Test workflow with unrestricted access.`,
+ filename: "unrestricted-workflow.md",
+ expectActionsWrite: false,
+ jobName: "",
+ description: "Unrestricted workflows should not need actions: write permission",
+ },
+ {
+ name: "main job should have actions: write when no task job",
+ frontmatter: `---
+on:
+ issues:
+ types: [opened]
+tools:
+ github:
+ allowed: [list_issues]
+---
+
+# Issues Workflow
+Test workflow with permission checks but no task job.`,
+ filename: "issues-workflow.md",
+ expectActionsWrite: true,
+ jobName: "issues-workflow",
+ description: "Main job should have actions: write for permission checks",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Create test file
+ testFile := filepath.Join(tmpDir, tt.filename)
+ err := os.WriteFile(testFile, []byte(tt.frontmatter), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Compile the workflow
+ err = compiler.CompileWorkflow(testFile)
+ if err != nil {
+ t.Fatalf("Failed to compile workflow: %v", err)
+ }
+
+ // Read the generated lock file
+ lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml"
+ lockContent, err := os.ReadFile(lockFile)
+ if err != nil {
+ t.Fatalf("Failed to read lock file: %v", err)
+ }
+ lockContentStr := string(lockContent)
+
+ if tt.expectActionsWrite {
+ // Check that the specified job exists
+ if !strings.Contains(lockContentStr, " "+tt.jobName+":") {
+ t.Fatalf("Job '%s' not found in workflow", tt.jobName)
+ }
+
+ // Check for actions: write permission anywhere in the workflow (should be in the right job)
+ if !strings.Contains(lockContentStr, "permissions:") || !strings.Contains(lockContentStr, "actions: write") {
+ t.Errorf("%s: Expected 'actions: write' permission in workflow but not found", tt.description)
+ }
+
+ // Check that setCancelled function is present
+ if !strings.Contains(lockContentStr, "async function setCancelled(message)") {
+ t.Errorf("%s: Expected custom setCancelled function but not found", tt.description)
+ }
+
+ // Check that github.rest.actions.cancelWorkflowRun is called
+ if !strings.Contains(lockContentStr, "github.rest.actions.cancelWorkflowRun") {
+ t.Errorf("%s: Expected self-cancellation API call but not found", tt.description)
+ }
+
+ } else {
+ // Check that actions: write permission is not present
+ if strings.Contains(lockContentStr, "actions: write") {
+ t.Errorf("%s: Did not expect 'actions: write' permission but found it", tt.description)
+ }
+ }
+ })
+ }
+}
+
+// TestMainJobActionsWritePermission tests that the main job gets actions: write
+// permission when permission checks are needed and no task job exists
+func TestMainJobActionsWritePermission(t *testing.T) {
+ // Create temporary directory for test files
+ tmpDir, err := os.MkdirTemp("", "main-job-actions-write-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ compiler := NewCompiler(false, "", "test")
+
+ testContent := `---
+on:
+ issues:
+ types: [opened]
+tools:
+ github:
+ allowed: [list_issues]
+---
+
+# Issues Workflow
+This workflow needs permission checks but has no task job.`
+
+ // Create test file
+ testFile := filepath.Join(tmpDir, "issues-workflow.md")
+ err = os.WriteFile(testFile, []byte(testContent), 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Compile the workflow
+ err = compiler.CompileWorkflow(testFile)
+ if err != nil {
+ t.Fatalf("Failed to compile workflow: %v", err)
+ }
+
+ // Read the generated lock file
+ lockFile := strings.TrimSuffix(testFile, ".md") + ".lock.yml"
+ lockContent, err := os.ReadFile(lockFile)
+ if err != nil {
+ t.Fatalf("Failed to read lock file: %v", err)
+ }
+ lockContentStr := string(lockContent)
+
+ // Check that the main job has actions: write permission
+ // Look for the main job (should be "issues-workflow" based on filename)
+ if !strings.Contains(lockContentStr, " issues-workflow:") {
+ t.Fatal("Main job 'issues-workflow' not found in workflow")
+ }
+
+ // Check for actions: write permission anywhere in the workflow
+ if !strings.Contains(lockContentStr, "permissions:") || !strings.Contains(lockContentStr, "actions: write") {
+ t.Error("Expected 'actions: write' permission in main job but not found")
+ }
+
+ // Check that permission check step is present
+ if !strings.Contains(lockContentStr, "Check team membership for workflow") {
+ t.Error("Expected team membership check step in main job but not found")
+ }
+
+ // Check that setCancelled function is present
+ if !strings.Contains(lockContentStr, "async function setCancelled(message)") {
+ t.Error("Expected custom setCancelled function but not found")
+ }
+}
diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go
index 07de406b4c5..bf806215200 100644
--- a/pkg/workflow/compiler.go
+++ b/pkg/workflow/compiler.go
@@ -122,33 +122,34 @@ func NewCompilerWithCustomOutput(verbose bool, engineOverride string, customOutp
// WorkflowData holds all the data needed to generate a GitHub Actions workflow
type WorkflowData struct {
- Name string
- FrontmatterName string // name field from frontmatter (for code scanning alert driver default)
- On string
- Permissions string
- Network string // top-level network permissions configuration
- Concurrency string
- RunName string
- Env string
- If string
- TimeoutMinutes string
- CustomSteps string
- PostSteps string // steps to run after AI execution
- RunsOn string
- Tools map[string]any
- MarkdownContent string
- AI string // "claude" or "codex" (for backwards compatibility)
- EngineConfig *EngineConfig // Extended engine configuration
- StopTime string
- Command string // for /command trigger support
- CommandOtherEvents map[string]any // for merging command with other events
- AIReaction string // AI reaction type like "eyes", "heart", etc.
- Jobs map[string]any // custom job configurations with dependencies
- Cache string // cache configuration
- NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }}
- NetworkPermissions *NetworkPermissions // parsed network permissions
- SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes
- Roles []string // permission levels required to trigger workflow
+ Name string
+ FrontmatterName string // name field from frontmatter (for code scanning alert driver default)
+ On string
+ Permissions string
+ Network string // top-level network permissions configuration
+ Concurrency string
+ RunName string
+ Env string
+ If string
+ TimeoutMinutes string
+ CustomSteps string
+ PostSteps string // steps to run after AI execution
+ RunsOn string
+ Tools map[string]any
+ MarkdownContent string
+ AI string // "claude" or "codex" (for backwards compatibility)
+ EngineConfig *EngineConfig // Extended engine configuration
+ StopTime string
+ Command string // for /command trigger support
+ CommandOtherEvents map[string]any // for merging command with other events
+ AIReaction string // AI reaction type like "eyes", "heart", etc.
+ Jobs map[string]any // custom job configurations with dependencies
+ Cache string // cache configuration
+ NeedsTextOutput bool // whether the workflow uses ${{ needs.task.outputs.text }}
+ NetworkPermissions *NetworkPermissions // parsed network permissions
+ SafeOutputs *SafeOutputsConfig // output configuration for automatic output routes
+ Roles []string // permission levels required to trigger workflow
+ HasExplicitGitHubTools bool // whether GitHub tools were explicitly configured (vs default)
}
// SafeOutputsConfig holds configuration for automatic output routes
@@ -1287,6 +1288,23 @@ func (c *Compiler) applyDefaults(data *WorkflowData, markdownPath string) {
if data.RunsOn == "" {
data.RunsOn = "runs-on: ubuntu-latest"
}
+
+ // Store whether tools were explicitly configured before applying defaults
+ hasExplicitTools := len(data.Tools) > 0
+ if hasExplicitTools {
+ // Check if github tools are explicitly configured with non-empty allowed list
+ if githubTools, hasGithub := data.Tools["github"]; hasGithub {
+ if githubMap, ok := githubTools.(map[string]any); ok {
+ if allowed, hasAllowed := githubMap["allowed"]; hasAllowed {
+ if allowedList, ok := allowed.([]any); ok && len(allowedList) > 0 {
+ // Mark that explicit GitHub tools are configured
+ data.HasExplicitGitHubTools = true
+ }
+ }
+ }
+ }
+ }
+
// Apply default tools
data.Tools = c.applyDefaultTools(data.Tools, data.SafeOutputs)
}
@@ -1755,11 +1773,8 @@ func (c *Compiler) needsPermissionChecks(data *WorkflowData) bool {
return false
}
- // Permission checks are needed by default unless workflow uses only safe events
- // Safe events: workflow_dispatch, workflow_run, schedule
- // For now, we'll implement a simple heuristic since we don't have frontmatter here
- // We'll implement the full logic later when we have access to frontmatter
- return true
+ // Only need permission checks if GitHub tools are explicitly configured
+ return data.HasExplicitGitHubTools
}
// needsPermissionChecksWithFrontmatter determines if the workflow needs permission checks with full context
@@ -1769,12 +1784,16 @@ func (c *Compiler) needsPermissionChecksWithFrontmatter(data *WorkflowData, fron
return false
}
+ // Only need permission checks if GitHub tools are explicitly configured
+ if !data.HasExplicitGitHubTools {
+ return false
+ }
+
// Check if the workflow uses only safe events (only if frontmatter is available)
if frontmatter != nil && c.hasSafeEventsOnly(data, frontmatter) {
return false
}
- // Permission checks are needed by default for non-safe events
return true
}
diff --git a/pkg/workflow/compiler_test.go b/pkg/workflow/compiler_test.go
index b7ab335f592..2c42dad155a 100644
--- a/pkg/workflow/compiler_test.go
+++ b/pkg/workflow/compiler_test.go
@@ -4202,13 +4202,17 @@ This workflow should get default permissions applied automatically.
lockContentStr := string(lockContent)
// Verify that default permissions are present in the generated workflow
+ // This test workflow has explicit GitHub tools (tools.github.allowed), so it should
+ // get specific permissions including actions: write for self-cancellation,
+ // NOT the default "read-all" permissions
expectedDefaultPermissions := []string{
- "read-all",
+ "actions: write", // Required for self-cancellation
+ "contents: read", // Basic read access
}
for _, expectedPerm := range expectedDefaultPermissions {
if !strings.Contains(lockContentStr, expectedPerm) {
- t.Errorf("Expected default permission '%s' not found in generated workflow.\nGenerated content:\n%s", expectedPerm, lockContentStr)
+ t.Errorf("Expected permission '%s' not found in generated workflow.\nGenerated content:\n%s", expectedPerm, lockContentStr)
}
}
@@ -4255,13 +4259,25 @@ This workflow should get default permissions applied automatically.
t.Fatal("Permissions section not found in main job")
}
- // Verify permissions is a map
- permissionsValue, ok := permissions.(string)
- if !ok {
- t.Fatal("Permissions section is not a string")
- }
- if permissionsValue != "read-all" {
- t.Fatal("Default permissions not read-all")
+ // Verify permissions is either a string or a map
+ if permissionsStr, ok := permissions.(string); ok {
+ // Handle string permissions (like "read-all")
+ if !strings.Contains(permissionsStr, "actions: write") {
+ t.Fatalf("Expected permissions to include 'actions: write' for workflows with explicit GitHub tools, got: %s", permissionsStr)
+ }
+ if !strings.Contains(permissionsStr, "contents: read") {
+ t.Fatalf("Expected permissions to include 'contents: read' for workflows with explicit GitHub tools, got: %s", permissionsStr)
+ }
+ } else if permissionsMap, ok := permissions.(map[string]interface{}); ok {
+ // Handle map permissions (YAML object)
+ if actionsVal, hasActions := permissionsMap["actions"]; !hasActions || actionsVal != "write" {
+ t.Fatalf("Expected permissions to include 'actions: write' for workflows with explicit GitHub tools, got: %v", permissionsMap)
+ }
+ if contentsVal, hasContents := permissionsMap["contents"]; !hasContents || contentsVal != "read" {
+ t.Fatalf("Expected permissions to include 'contents: read' for workflows with explicit GitHub tools, got: %v", permissionsMap)
+ }
+ } else {
+ t.Fatalf("Permissions section is neither a string nor a map, got type: %T, value: %v", permissions, permissions)
}
}
diff --git a/pkg/workflow/js.go b/pkg/workflow/js.go
index 154ed7897f7..758cf14cf9d 100644
--- a/pkg/workflow/js.go
+++ b/pkg/workflow/js.go
@@ -51,9 +51,6 @@ var addReactionAndEditCommentScript string
//go:embed js/check_permissions.cjs
var checkPermissionsScript string
-//go:embed js/check_team_member.cjs
-var checkTeamMemberScript string
-
//go:embed js/parse_claude_log.cjs
var parseClaudeLogScript string
diff --git a/pkg/workflow/js/check_permissions.cjs b/pkg/workflow/js/check_permissions.cjs
index 1b14376fe56..f6559b276c2 100644
--- a/pkg/workflow/js/check_permissions.cjs
+++ b/pkg/workflow/js/check_permissions.cjs
@@ -1,19 +1,22 @@
-// Custom setCancelled function that uses self-cancellation
+/**
+ * Custom setCancelled function that uses self-cancellation
+ * @param {string} message - The cancellation message
+ */
async function setCancelled(message) {
try {
// Cancel the current workflow run using GitHub Actions API
await github.rest.actions.cancelWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
- run_id: context.runId
+ run_id: context.runId,
});
core.info(`Cancellation requested for this workflow run: ${message}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
+ // Fallback to core.setFailed if API call fails (since core.setCancelled doesn't exist in types)
+ core.setFailed(message);
}
}
diff --git a/pkg/workflow/js/check_team_member.cjs b/pkg/workflow/js/check_team_member.cjs
index a84b52e8c53..8610e78e901 100644
--- a/pkg/workflow/js/check_team_member.cjs
+++ b/pkg/workflow/js/check_team_member.cjs
@@ -1,19 +1,22 @@
-// Custom setCancelled function that uses self-cancellation
+/**
+ * Custom setCancelled function that uses self-cancellation
+ * @param {string} message - The cancellation message
+ */
async function setCancelled(message) {
try {
// Cancel the current workflow run using GitHub Actions API
await github.rest.actions.cancelWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
- run_id: context.runId
+ run_id: context.runId,
});
core.info(`Cancellation requested for this workflow run: ${message}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
core.warning(`Failed to cancel workflow run: ${errorMessage}`);
- // Fallback to core.setCancelled if API call fails
- core.setCancelled(message);
+ // Fallback to core.setFailed if API call fails (since core.setCancelled doesn't exist in types)
+ core.setFailed(message);
}
}
@@ -51,12 +54,12 @@ async function main() {
// Team membership check failed - use self-cancellation
const failureMessage = `Access denied: User '${actor}' is not authorized to trigger this workflow. Only admin or maintainer users can run this workflow.`;
core.warning(`❌ ${failureMessage}`);
-
+
await setCancelled(failureMessage);
-
+
// Set output for any dependent steps that might check before cancellation takes effect
core.setOutput("is_team_member", "false");
-
+
// Return to finish the script
return;
}
diff --git a/pkg/workflow/js/check_team_member.test.cjs b/pkg/workflow/js/check_team_member.test.cjs
index 2c3501aa031..e4a9017de50 100644
--- a/pkg/workflow/js/check_team_member.test.cjs
+++ b/pkg/workflow/js/check_team_member.test.cjs
@@ -9,6 +9,7 @@ const mockCore = {
error: vi.fn(),
info: vi.fn(),
setCancelled: vi.fn(),
+ setFailed: vi.fn(),
};
const mockGithub = {
@@ -189,7 +190,9 @@ describe("check_team_member.cjs", () => {
expect.stringContaining("Cancellation requested for this workflow run")
);
expect(mockCore.warning).toHaveBeenCalledWith(
- expect.stringContaining("Access denied: User 'testuser' is not authorized")
+ expect.stringContaining(
+ "Access denied: User 'testuser' is not authorized"
+ )
);
expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false");
@@ -229,7 +232,9 @@ describe("check_team_member.cjs", () => {
"Failed to cancel workflow run: API Error: Forbidden"
);
expect(mockCore.setCancelled).toHaveBeenCalledWith(
- expect.stringContaining("Access denied: User 'testuser' is not authorized")
+ expect.stringContaining(
+ "Access denied: User 'testuser' is not authorized"
+ )
);
expect(mockCore.setOutput).toHaveBeenCalledWith("is_team_member", "false");
diff --git a/pkg/workflow/js/compute_text.cjs b/pkg/workflow/js/compute_text.cjs
index ed74e08fcb0..d0100a81e45 100644
--- a/pkg/workflow/js/compute_text.cjs
+++ b/pkg/workflow/js/compute_text.cjs
@@ -37,8 +37,6 @@ function sanitizeContent(content) {
// XML tag neutralization - convert XML tags to parentheses format
sanitized = convertXmlTagsToParentheses(sanitized);
-
-
// URI filtering - replace non-https protocols with "(redacted)"
// Step 1: Temporarily mark HTTPS URLs to protect them
sanitized = sanitizeUrlProtocols(sanitized);
diff --git a/pkg/workflow/js/compute_text.test.cjs b/pkg/workflow/js/compute_text.test.cjs
index b72d9a03e57..5763606ee5d 100644
--- a/pkg/workflow/js/compute_text.test.cjs
+++ b/pkg/workflow/js/compute_text.test.cjs
@@ -100,7 +100,8 @@ describe("compute_text.cjs", () => {
});
it("should handle self-closing XML tags without whitespace", () => {
- const input = 'Self-closing:
';
+ const input =
+ 'Self-closing:
';
const result = sanitizeContentFunction(input);
expect(result).toContain("(br/)");
expect(result).toContain('(img src="test.jpg"/)');
@@ -108,7 +109,8 @@ describe("compute_text.cjs", () => {
});
it("should handle self-closing XML tags with whitespace", () => {
- const input = 'With spaces:
';
+ const input =
+ 'With spaces:
';
const result = sanitizeContentFunction(input);
expect(result).toContain("(br /)");
expect(result).toContain('(img src="test.jpg" /)');
@@ -116,7 +118,8 @@ describe("compute_text.cjs", () => {
});
it("should handle XML tags with various whitespace patterns", () => {
- const input = 'Various:
';
+ const input =
+ 'Self-closing:
';
const result = sanitizeContentFunction(input);
expect(result).toContain("(br/)");
expect(result).toContain('(img src="test.jpg"/)');
@@ -98,7 +99,8 @@ describe("sanitize_output.cjs", () => {
});
it("should handle self-closing XML tags with whitespace", () => {
- const input = 'With spaces:
';
+ const input =
+ 'With spaces:
';
const result = sanitizeContentFunction(input);
expect(result).toContain("(br /)");
expect(result).toContain('(img src="test.jpg" /)');
@@ -106,7 +108,8 @@ describe("sanitize_output.cjs", () => {
});
it("should handle XML tags with various whitespace patterns", () => {
- const input = 'Various: