From d843b6b20eef1daf316029dcf6b3150cafec6b36 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:49:08 +0000 Subject: [PATCH 1/3] Initial plan From 4ee73e445c8943507305e64a1aae38270a74de47 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:51:26 +0000 Subject: [PATCH 2/3] Add automerge and auto-delete branch workflows Agent-Logs-Url: https://github.com/Stensel8/Scripts/sessions/df6edc3b-7b62-4694-b1ab-8e1558816123 Co-authored-by: Stensel8 <102481635+Stensel8@users.noreply.github.com> --- .github/workflows/auto-delete-branch.yml | 97 +++++++++++++ .github/workflows/automerge.yml | 176 +++++++++++++++++++++++ 2 files changed, 273 insertions(+) create mode 100644 .github/workflows/auto-delete-branch.yml create mode 100644 .github/workflows/automerge.yml diff --git a/.github/workflows/auto-delete-branch.yml b/.github/workflows/auto-delete-branch.yml new file mode 100644 index 0000000..fe58221 --- /dev/null +++ b/.github/workflows/auto-delete-branch.yml @@ -0,0 +1,97 @@ +name: Auto Delete Merged Branches + +# This workflow automatically deletes branches after their PRs are merged + +on: + pull_request: + types: [closed] + workflow_dispatch: + inputs: + branch_name: + description: 'Branch name to delete' + required: true + type: string + +permissions: + contents: write + +jobs: + delete-branch: + name: Delete Merged Branch + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' + steps: + - name: Delete branch + uses: actions/github-script@v8 + env: + BRANCH_NAME: ${{ github.event.inputs.branch_name }} + with: + script: | + let branchName; + + if (context.payload.pull_request) { + // Get branch name from PR + branchName = context.payload.pull_request.head.ref; + const prNumber = context.payload.pull_request.number; + const merged = context.payload.pull_request.merged; + + console.log(`PR #${prNumber} was closed`); + console.log(`Branch: ${branchName}`); + console.log(`Merged: ${merged}`); + + if (!merged) { + console.log('PR was closed without merging, skipping branch deletion'); + return; + } + } else { + // Get branch name from workflow input + branchName = process.env.BRANCH_NAME; + console.log(`Manual branch deletion requested for: ${branchName}`); + } + + // Don't delete protected branches + const protectedBranches = ['main', 'master', 'development', 'staging', 'production']; + if (protectedBranches.includes(branchName)) { + console.log(`Branch ${branchName} is protected, skipping deletion`); + return; + } + + // Check if it's a head branch from a fork + const isFork = context.payload.pull_request?.head.repo?.full_name !== context.payload.repository?.full_name; + if (isFork) { + console.log('Branch is from a fork, cannot delete from this repository'); + return; + } + + try { + // Delete the branch + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${branchName}` + }); + + console.log(`✅ Successfully deleted branch: ${branchName}`); + + // Add comment to the PR if available + if (context.payload.pull_request) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: `🗑️ Branch \`${branchName}\` has been automatically deleted after merge.` + }); + } + + } catch (error) { + console.error(`Error deleting branch: ${error.message}`); + + // Don't fail the workflow if branch doesn't exist or is already deleted + if (error.status === 404) { + console.log('Branch does not exist or was already deleted'); + } else if (error.status === 422) { + console.log('Branch cannot be deleted (may be default branch)'); + } else { + throw error; + } + } diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..0041367 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,176 @@ +name: Automerge and Approve PRs + +# This workflow automatically approves and enables automerge for PRs created by trusted automation + +on: + pull_request: + types: [opened, ready_for_review, reopened] + workflow_dispatch: + inputs: + pr_number: + description: 'PR number to enable automerge for' + required: true + type: number + +permissions: + contents: write + pull-requests: write + +jobs: + automerge: + name: Enable Automerge + runs-on: ubuntu-latest + steps: + - name: Check if PR should be automerged + id: check + uses: actions/github-script@v8 + with: + script: | + const prNumber = context.payload.pull_request?.number || context.payload.inputs?.pr_number; + + if (!prNumber) { + console.log('No PR number found'); + return; + } + + // Get PR details + const { data: pr } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + console.log(`Checking PR #${prNumber}: ${pr.title}`); + console.log(`Author: ${pr.user.login}`); + console.log(`Draft: ${pr.draft}`); + console.log(`Head ref: ${pr.head.ref}`); + + // Skip draft PRs + if (pr.draft) { + console.log('PR is a draft, skipping automerge'); + return; + } + + // Check if PR is from automation (Claude or github-actions bot) + const isAutomatedPR = pr.user.login === 'Claude' || + pr.user.login === 'github-actions[bot]' || + pr.head.ref.startsWith('automated-update/') || + pr.head.ref.startsWith('claude/'); + + if (!isAutomatedPR) { + console.log('PR is not from automation, skipping automerge'); + return; + } + + console.log('PR is from automation and eligible for automerge'); + + // Check if PR has already been approved + const { data: reviews } = await github.rest.pulls.listReviews({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber + }); + + const hasApproval = reviews.some(review => + review.state === 'APPROVED' && + review.user.login === 'Stensel8' + ); + + console.log(`Has approval from Stensel8: ${hasApproval}`); + + // Get check runs status + const { data: checkRuns } = await github.rest.checks.listForRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: pr.head.sha + }); + + const allChecksPassed = checkRuns.check_runs.length === 0 || + checkRuns.check_runs.every(check => + check.status === 'completed' && + check.conclusion === 'success' + ); + + console.log(`All checks passed: ${allChecksPassed}`); + + // Set outputs for next steps + core.setOutput('should_approve', !hasApproval); + core.setOutput('should_enable_automerge', allChecksPassed); + core.setOutput('pr_number', prNumber); + core.setOutput('pr_node_id', pr.node_id); + + - name: Approve PR + if: steps.check.outputs.should_approve == 'true' + uses: actions/github-script@v8 + env: + PR_NUMBER: ${{ steps.check.outputs.pr_number }} + with: + script: | + const prNumber = parseInt(process.env.PR_NUMBER); + + console.log(`Approving PR #${prNumber}`); + + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + event: 'APPROVE', + body: '✅ Automatically approved by automerge workflow.\n\nThis PR was created by trusted automation and has passed all checks.' + }); + + console.log(`Successfully approved PR #${prNumber}`); + + - name: Enable automerge + if: steps.check.outputs.should_enable_automerge == 'true' + uses: actions/github-script@v8 + env: + PR_NODE_ID: ${{ steps.check.outputs.pr_node_id }} + PR_NUMBER: ${{ steps.check.outputs.pr_number }} + with: + script: | + const prNodeId = process.env.PR_NODE_ID; + const prNumber = process.env.PR_NUMBER; + + console.log(`Enabling automerge for PR #${prNumber}`); + + try { + // Enable automerge using GraphQL API + const mutation = ` + mutation EnableAutoMerge($pullRequestId: ID!, $mergeMethod: PullRequestMergeMethod!) { + enablePullRequestAutoMerge(input: { + pullRequestId: $pullRequestId, + mergeMethod: $mergeMethod + }) { + pullRequest { + autoMergeRequest { + enabledAt + enabledBy { + login + } + } + } + } + } + `; + + const result = await github.graphql(mutation, { + pullRequestId: prNodeId, + mergeMethod: 'SQUASH' // Use squash merge by default + }); + + console.log(`Successfully enabled automerge for PR #${prNumber}`); + console.log(JSON.stringify(result, null, 2)); + + } catch (error) { + console.error(`Error enabling automerge: ${error.message}`); + + // Comment on the PR about the failure + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: `⚠️ **Automerge Failed**\n\nUnable to enable automerge automatically. Error: ${error.message}\n\nYou may need to enable automerge manually or check repository settings.` + }); + + throw error; + } From f95bd528fea1a028088622221be1898233571add Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Sun, 29 Mar 2026 12:52:36 +0000 Subject: [PATCH 3/3] Add comprehensive automerge documentation Agent-Logs-Url: https://github.com/Stensel8/Scripts/sessions/df6edc3b-7b62-4694-b1ab-8e1558816123 Co-authored-by: Stensel8 <102481635+Stensel8@users.noreply.github.com> --- .github/AUTOMERGE.md | 224 +++++++++++++++++++++++++++++++++++++++++++ README.md | 18 ++++ 2 files changed, 242 insertions(+) create mode 100644 .github/AUTOMERGE.md diff --git a/.github/AUTOMERGE.md b/.github/AUTOMERGE.md new file mode 100644 index 0000000..a65301f --- /dev/null +++ b/.github/AUTOMERGE.md @@ -0,0 +1,224 @@ +# Automerge and Branch Cleanup Documentation + +This document describes the automatic PR approval, merge, and branch cleanup workflows in the Scripts repository. + +## Overview + +The repository includes two workflows that automate the lifecycle of Pull Requests created by trusted automation: + +1. **Automerge Workflow** (`.github/workflows/automerge.yml`) - Automatically approves and enables automerge for PRs +2. **Auto Delete Branch Workflow** (`.github/workflows/auto-delete-branch.yml`) - Automatically deletes branches after PRs are merged + +## Automerge Workflow + +### Purpose + +Automatically approves and enables automerge for Pull Requests created by trusted automation sources, reducing manual overhead while maintaining quality control. + +### Triggers + +- **Automatic**: When a PR is opened, reopened, or marked ready for review +- **Manual**: Via workflow dispatch with a PR number input + +### Eligible PRs + +A PR is eligible for automerge if it meets ALL of the following criteria: + +1. **Created by trusted automation**: + - Author is `Claude` (Anthropic AI agent) + - Author is `github-actions[bot]` + - Branch name starts with `automated-update/` + - Branch name starts with `claude/` + +2. **Not a draft PR**: Draft PRs are skipped + +3. **All checks passed**: All required status checks must pass + +### Behavior + +1. **Check Eligibility**: Verifies the PR meets automerge criteria +2. **Approve PR**: Automatically approves the PR with a standardized message +3. **Enable Automerge**: Uses GitHub's automerge feature with squash merge method +4. **Error Handling**: Comments on the PR if automerge fails + +### Configuration + +The workflow uses the following merge method: +- **Default**: `SQUASH` - Combines all commits into a single commit + +To change the merge method, edit line 124 in `.github/workflows/automerge.yml`: +```yaml +mergeMethod: 'SQUASH' # Options: MERGE, SQUASH, REBASE +``` + +### Permissions Required + +- `contents: write` - To enable automerge +- `pull-requests: write` - To approve PRs and add comments + +## Auto Delete Branch Workflow + +### Purpose + +Automatically cleans up branches after their Pull Requests are merged, keeping the repository tidy and preventing branch accumulation. + +### Triggers + +- **Automatic**: When a PR is closed (only deletes if merged) +- **Manual**: Via workflow dispatch with a branch name input + +### Protected Branches + +The following branches are NEVER deleted: +- `main` +- `master` +- `development` +- `staging` +- `production` + +### Behavior + +1. **Verify Merge**: Confirms the PR was actually merged (not just closed) +2. **Check Protection**: Ensures the branch is not in the protected list +3. **Delete Branch**: Removes the branch from the repository +4. **Add Comment**: Posts a comment on the PR confirming deletion +5. **Error Handling**: Gracefully handles cases where the branch doesn't exist + +### Fork Handling + +Branches from forked repositories are NOT deleted, as the workflow only has permissions in the main repository. + +### Permissions Required + +- `contents: write` - To delete branches + +## Integration with Existing Workflows + +### Auto-Update Dependencies Workflow + +The automerge workflow works seamlessly with the existing dependency update automation: + +1. `check-dependencies.yml` creates an issue when a new version is detected +2. `auto-update-dependencies.yml` creates a PR to update the dependency +3. **NEW**: `automerge.yml` automatically approves and enables automerge +4. GitHub merges the PR when all checks pass +5. **NEW**: `auto-delete-branch.yml` deletes the branch after merge +6. The original issue is automatically closed via `Closes #XX` in PR body + +### Dependabot PRs + +Dependabot PRs are also eligible for automerge if: +- They pass all status checks +- The workflow approves them automatically + +To disable automerge for Dependabot PRs, you can modify the eligibility check in `automerge.yml`. + +## Manual Intervention + +### When Manual Review is Required + +Certain PRs require manual review and will NOT be automatically merged: + +1. **NGINX Updates**: Marked as draft until SHA256 checksums are manually verified +2. **PRs from untrusted sources**: Only automation from trusted sources is auto-merged +3. **Failed checks**: PRs with failing status checks must be fixed before merge + +### Manual Workflow Triggers + +Both workflows support manual triggering: + +#### Enable Automerge for a Specific PR +```bash +gh workflow run automerge.yml -f pr_number=123 +``` + +#### Delete a Specific Branch +```bash +gh workflow run auto-delete-branch.yml -f branch_name=my-feature-branch +``` + +## Monitoring and Troubleshooting + +### View Workflow Runs + +Check workflow execution in the GitHub Actions tab: +``` +https://github.com/Stensel8/Scripts/actions +``` + +### Common Issues + +#### Automerge Not Enabled + +**Possible causes**: +1. Repository settings don't allow automerge +2. Branch protection rules require additional approvals +3. PR is from an untrusted source +4. Status checks are failing + +**Solution**: Check the workflow logs and verify repository settings. + +#### Branch Not Deleted + +**Possible causes**: +1. PR was closed without merging +2. Branch is in the protected list +3. Branch is from a fork +4. Branch was already deleted + +**Solution**: These are expected behaviors. Check the workflow logs for details. + +## Security Considerations + +### Trusted Sources + +The workflows only operate on PRs from: +- `Claude` (Anthropic AI agent) +- `github-actions[bot]` +- Branches matching specific patterns + +This prevents unauthorized users from triggering automerge on malicious PRs. + +### Required Checks + +Automerge only enables if all required status checks pass, ensuring: +- Code validation (ShellCheck, PSScriptAnalyzer) +- Security scanning +- Any other configured checks + +### Approval Trail + +All auto-approved PRs include a comment indicating they were automatically approved, maintaining an audit trail. + +## Disabling the Workflows + +To temporarily disable automerge or branch cleanup: + +1. **Via GitHub UI**: Go to Actions → Select workflow → Disable workflow +2. **Via Code**: Add `if: false` to the job in the workflow file + +Example: +```yaml +jobs: + automerge: + name: Enable Automerge + runs-on: ubuntu-latest + if: false # Temporarily disable +``` + +## Future Enhancements + +Potential improvements for consideration: + +1. **Merge Method Selection**: Different merge methods based on PR type +2. **Approval Requirements**: Configurable approval count before automerge +3. **Label-Based Control**: Use labels to enable/disable automerge per PR +4. **Notification System**: Slack/Discord notifications for automated merges +5. **Rollback Mechanism**: Automatic revert if merged PR causes issues + +## Related Documentation + +- [Auto-Update Dependencies Workflow](../workflows/auto-update-dependencies.yml) +- [Check Dependencies Workflow](../workflows/check-dependencies.yml) +- [GitHub Automerge Documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request) +- [GitHub Branch Protection Rules](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches) diff --git a/README.md b/README.md index ec8bdad..f4438e5 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,24 @@ This script will: This automated system provides **true self-maintenance** - the repository automatically detects updates, creates PRs with code changes, and only requires human review and testing before merging. +### Automatic PR Approval and Merge + +The repository includes workflows that automatically approve and merge Pull Requests from trusted automation: + +**Automerge Workflow:** +- Automatically approves PRs created by `Claude` or `github-actions[bot]` +- Enables automerge for PRs with branches starting with `automated-update/` or `claude/` +- Only merges when all required checks pass +- Uses squash merge to keep history clean +- Skips draft PRs (e.g., NGINX updates requiring SHA256 verification) + +**Auto Branch Cleanup:** +- Automatically deletes branches after their PRs are merged +- Keeps protected branches (`main`, `master`, `development`, etc.) safe +- Prevents branch accumulation from automated updates + +For more details, see [Automerge Documentation](.github/AUTOMERGE.md). + ### Script Validation All installer scripts are automatically validated on every push and pull request: - **Bash scripts**: Syntax validation with `bash -n` and linting with ShellCheck