diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index 18f508ac169..1f07d6327bd 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -45,6 +45,8 @@ run-name: "Daily Issues Report Generator" jobs: activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' runs-on: ubuntu-slim permissions: contents: read @@ -7421,6 +7423,155 @@ jobs: path: /tmp/gh-aw/threat-detection/detection.log if-no-files-found: ignore + pre_activation: + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + steps: + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: admin,maintainer,write + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + function parseRequiredPermissions() { + const requiredPermissionsEnv = process.env.GH_AW_REQUIRED_ROLES; + return requiredPermissionsEnv ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") : []; + } + function parseAllowedBots() { + const allowedBotsEnv = process.env.GH_AW_ALLOWED_BOTS; + return allowedBotsEnv ? allowedBotsEnv.split(",").filter(b => b.trim() !== "") : []; + } + async function checkBotStatus(actor, owner, repo) { + try { + const isBot = actor.endsWith("[bot]"); + if (!isBot) { + return { isBot: false, isActive: false }; + } + core.info(`Checking if bot '${actor}' is active on ${owner}/${repo}`); + try { + const botPermission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: owner, + repo: repo, + username: actor, + }); + core.info(`Bot '${actor}' is active with permission level: ${botPermission.data.permission}`); + return { isBot: true, isActive: true }; + } catch (botError) { + if (typeof botError === "object" && botError !== null && "status" in botError && botError.status === 404) { + core.warning(`Bot '${actor}' is not active/installed on ${owner}/${repo}`); + return { isBot: true, isActive: false }; + } + const errorMessage = botError instanceof Error ? botError.message : String(botError); + core.warning(`Failed to check bot status: ${errorMessage}`); + return { isBot: true, isActive: false, error: errorMessage }; + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + core.warning(`Error checking bot status: ${errorMessage}`); + return { isBot: false, isActive: false, error: errorMessage }; + } + } + async function checkRepositoryPermission(actor, owner, repo, requiredPermissions) { + try { + core.info(`Checking if user '${actor}' has required permissions for ${owner}/${repo}`); + core.info(`Required permissions: ${requiredPermissions.join(", ")}`); + const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({ + owner: owner, + repo: repo, + username: actor, + }); + const permission = repoPermission.data.permission; + core.info(`Repository permission level: ${permission}`); + for (const requiredPerm of requiredPermissions) { + if (permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain")) { + core.info(`✅ User has ${permission} access to repository`); + return { authorized: true, permission: permission }; + } + } + core.warning(`User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`); + return { authorized: false, permission: permission }; + } catch (repoError) { + const errorMessage = repoError instanceof Error ? repoError.message : String(repoError); + core.warning(`Repository permission check failed: ${errorMessage}`); + return { authorized: false, error: errorMessage }; + } + } + async function main() { + const { eventName } = context; + const actor = context.actor; + const { owner, repo } = context.repo; + const requiredPermissions = parseRequiredPermissions(); + const allowedBots = parseAllowedBots(); + if (eventName === "workflow_dispatch") { + const hasWriteRole = requiredPermissions.includes("write"); + if (hasWriteRole) { + core.info(`✅ Event ${eventName} does not require validation (write role allowed)`); + core.setOutput("is_team_member", "true"); + core.setOutput("result", "safe_event"); + return; + } + core.info(`Event ${eventName} requires validation (write role not allowed)`); + } + const safeEvents = ["schedule"]; + if (safeEvents.includes(eventName)) { + core.info(`✅ Event ${eventName} does not require validation`); + core.setOutput("is_team_member", "true"); + core.setOutput("result", "safe_event"); + return; + } + if (!requiredPermissions || requiredPermissions.length === 0) { + core.warning("❌ Configuration error: Required permissions not specified. Contact repository administrator."); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "config_error"); + core.setOutput("error_message", "Configuration error: Required permissions not specified"); + return; + } + const result = await checkRepositoryPermission(actor, owner, repo, requiredPermissions); + if (result.error) { + core.setOutput("is_team_member", "false"); + core.setOutput("result", "api_error"); + core.setOutput("error_message", `Repository permission check failed: ${result.error}`); + return; + } + if (result.authorized) { + core.setOutput("is_team_member", "true"); + core.setOutput("result", "authorized"); + core.setOutput("user_permission", result.permission); + } else { + if (allowedBots && allowedBots.length > 0) { + core.info(`Checking if actor '${actor}' is in allowed bots list: ${allowedBots.join(", ")}`); + if (allowedBots.includes(actor)) { + core.info(`Actor '${actor}' is in the allowed bots list`); + const botStatus = await checkBotStatus(actor, owner, repo); + if (botStatus.isBot && botStatus.isActive) { + core.info(`✅ Bot '${actor}' is active on the repository and authorized`); + core.setOutput("is_team_member", "true"); + core.setOutput("result", "authorized_bot"); + core.setOutput("user_permission", "bot"); + return; + } else if (botStatus.isBot && !botStatus.isActive) { + core.warning(`Bot '${actor}' is in the allowed list but not active/installed on ${owner}/${repo}`); + core.setOutput("is_team_member", "false"); + core.setOutput("result", "bot_not_active"); + core.setOutput("user_permission", result.permission); + core.setOutput("error_message", `Access denied: Bot '${actor}' is not active/installed on this repository`); + return; + } else { + core.info(`Actor '${actor}' is in allowed bots list but bot status check failed`); + } + } + } + core.setOutput("is_team_member", "false"); + core.setOutput("result", "insufficient_permissions"); + core.setOutput("user_permission", result.permission); + core.setOutput("error_message", `Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`); + } + } + await main(); + safe_outputs: needs: - agent diff --git a/.github/workflows/daily-issues-report.md b/.github/workflows/daily-issues-report.md index 204fd04b772..c3d430e0254 100644 --- a/.github/workflows/daily-issues-report.md +++ b/.github/workflows/daily-issues-report.md @@ -1,8 +1,6 @@ --- description: Daily report analyzing repository issues with clustering, metrics, and trend charts -on: - schedule: daily - workflow_dispatch: +on: daily permissions: contents: read actions: read diff --git a/README.md b/README.md index e5d8957ef92..f37e7336072 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,8 @@ GitHub Agentic Workflows transforms natural language markdown files into GitHub ```markdown --- -on: - schedule: - - cron: "0 6 * * *" - +on: daily permissions: read - safe-outputs: create-discussion: --- diff --git a/docs/src/content/docs/examples/scheduled.md b/docs/src/content/docs/examples/scheduled.md index 0566f8bfe48..2bd928e440c 100644 --- a/docs/src/content/docs/examples/scheduled.md +++ b/docs/src/content/docs/examples/scheduled.md @@ -21,6 +21,14 @@ Scheduled workflows run automatically at specified times using cron expressions. ## Example Schedule Triggers +**Recommended: Short fuzzy syntax** +```yaml +on: daily # Automatically scattered to different time +on: weekly # Scattered across days and times +on: weekly on monday # Scattered time on specific day +``` + +**Traditional cron syntax** ```yaml on: schedule: @@ -29,6 +37,8 @@ on: - cron: "0 */6 * * *" # Every 6 hours ``` +See the [Triggers reference](/gh-aw/reference/triggers/#scheduled-triggers-schedule) for complete syntax options. + ## Quick Start Add a scheduled workflow to your repository: