github-branch-policy

Audit GitHub repository branch governance and workflow hygiene. Use when asked to review rulesets, required status checks, update restrictions, bypass/update behavior, delete-on-merge settings, auto-merge workflow reliability, stale branches, ghost workflow registrations, or branch-policy drift.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "github-branch-policy" with this command: npx skills add oceanswave/i-know-kung-fu/oceanswave-i-know-kung-fu-github-branch-policy

GitHub Branch Policy

Overview

Run a repeatable audit for GitHub branch policy safety and Actions workflow hygiene. Validate ruleset enforcement, required checks, workflow registration integrity, and branch cleanup behavior that commonly break CI and auto-merge.

This skill explicitly checks for real-world failure modes where:

  • auto-merge is enabled and checks are green, but PRs remain BEHIND/BLOCKED, and
  • auto-merge enablement fails transiently (for example GitHub API 502), leaving PRs stranded.

Use This Skill When

Apply this skill for requests like:

  • "Audit branch protection/rulesets on this repo."
  • "Check whether auto-merge and branch cleanup are configured correctly."
  • "Find ghost workflows or stale branches causing Actions failures."
  • "Why are we getting Cannot update this protected ref?"
  • "Why is auto-merge enabled but nothing merges?"
  • "Make sure branch policy matches solo-developer expectations."

Prerequisites

  • gh authenticated for the target repo (repo + workflow scopes).
  • jq available.
  • Target repository known as OWNER/REPO, or current directory is a checked-out repo with a GitHub remote.

Quick Setup

OWNER_REPO="${OWNER_REPO:-$(gh repo view --json nameWithOwner -q .nameWithOwner)}"
OWNER="${OWNER_REPO%/*}"
REPO="${OWNER_REPO#*/}"
DEFAULT_BRANCH="$(gh repo view "$OWNER_REPO" --json defaultBranchRef -q .defaultBranchRef.name)"
echo "Auditing $OWNER_REPO (default: $DEFAULT_BRANCH)"

Known Good Baseline (Solo-Maintainer Friendly)

  • Repository has allow_auto_merge: true.
  • Active default-branch ruleset includes pull_request and required_status_checks.
  • Required status check contexts exactly match live check names.
  • Default-branch ruleset has no bypass actors (bypass_actors: []).
  • Required status check contexts are limited to merge-critical gates; non-critical provider checks (for example Vercel Preview Comments) may not be present and may unintentionally break auto-merge.
  • update rule is optional:
    • If enabled, bypass actors must be intentionally configured.
    • If not needed for your workflow, remove it to prevent unnecessary BLOCKED states.
  • Branch updater workflow is not push-only (has at least one fallback trigger such as pull_request_target, schedule, or workflow_dispatch).
  • Branch updater verifies required CI checks are present on the latest PR head SHA after any update-branch operation.
  • Auto-merge workflow tolerates transient API failures (retry/backoff).
  • delete_branch_on_merge: true (or equivalent cleanup automation).

Audit Checklist

1. Repository merge settings are compatible with policy

Verification:

gh api "repos/$OWNER/$REPO" \
  --jq '{allow_auto_merge,allow_squash_merge,allow_merge_commit,allow_rebase_merge,delete_branch_on_merge,default_branch}'

Pass criteria:

  • allow_auto_merge: true when auto-merge is expected.
  • Merge methods match branch rules (for example, squash-only policy means squash is enabled).
  • delete_branch_on_merge: true unless intentionally disabled.

Remediation:

  • Enable auto-merge at repo level.
  • Align repo merge methods with ruleset allowed_merge_methods.
  • Enable delete-on-merge, or document why not.

2. Active ruleset applies to default branch and enforces PR + required checks

Verification:

gh api "repos/$OWNER/$REPO/rulesets" \
  --jq '.[] | {id,name,enforcement,target,include:(.conditions.ref_name.include // []),rules:[.rules[].type]}'
gh api "repos/$OWNER/$REPO/rulesets" \
  --jq '.[] | select(.enforcement=="active") | .rules[] | select(.type=="pull_request" or .type=="required_status_checks" or .type=="update")'

Pass criteria:

  • At least one active branch ruleset applies to ~DEFAULT_BRANCH (or equivalent explicit default branch include).
  • Ruleset includes pull_request and required_status_checks.
  • update may be present or absent, but must be intentional.

Remediation:

  • Enable or create a default-branch ruleset.
  • Add missing pull_request and required_status_checks rules.
  • Remove accidental update if it is not part of your branch update strategy.

3. Required check contexts match real check names

Verification:

gh api "repos/$OWNER/$REPO/rulesets" \
  --jq '.[] | .rules[] | select(.type=="required_status_checks") | .parameters.required_status_checks[].context'
gh pr list --state all --limit 20 --json number \
  --jq '.[0].number' | xargs -I{} gh pr view {} --json statusCheckRollup

Pass criteria:

  • Required contexts exactly match real checks reported on PRs (case-sensitive), for example ci, Vercel, Vercel Preview Comments.

Remediation:

  • Update required check contexts in the ruleset to match actual check names.

4. update rule and bypass actors are compatible with your auto-merge flow

Verification:

gh api "repos/$OWNER/$REPO/rulesets" \
  --jq '.[] | select(.enforcement=="active" and .target=="branch") |
    {id,name,has_update:([.rules[].type] | index("update") != null),
     bypass_actors:((.bypass_actors // []) | map({actor_type,actor_id,bypass_mode}))}'

Pass criteria:

  • If has_update is false, this is acceptable when no branch-update automation is required.
  • If has_update is true, bypass actors and modes are intentionally set so trusted maintainers/automation can still complete merges.

Remediation:

  • For solo-maintainer repos, prefer removing unneeded update requirements.
  • Remove bypass actors from the default-branch ruleset.
  • If emergency bypass is temporarily required, keep scope minimal, time-box it, document owner approval, and remove it immediately after incident resolution.
  • Re-test with a smoke PR after policy changes.

5. Solo-dev compatibility: CODEOWNERS review is not forced unless intentional

Verification:

gh api "repos/$OWNER/$REPO/rulesets" \
  --jq '.[] | {name, pull_request_rules:[.rules[] | select(.type=="pull_request") | .parameters.require_code_owner_review]}'

Pass criteria:

  • For solo-maintainer repos, require_code_owner_review is false unless intentionally required.

Remediation:

  • Set require_code_owner_review: false where solo-dev flow is desired.

6. Actions policy allows the workflow dependencies you actually use

Verification:

gh api "repos/$OWNER/$REPO/actions/permissions" \
  --jq '{enabled,allowed_actions,sha_pinning_required}'
gh api "repos/$OWNER/$REPO/actions/permissions/selected-actions" \
  --jq '{github_owned_allowed,verified_allowed,patterns_allowed}'

Pass criteria:

  • If allowed_actions: selected, all actions used by workflows are explicitly allowed (or covered by allowed classes).
  • If SHA pinning is required, actions are pinned.

Remediation:

  • Add missing action patterns to selected-actions policy.
  • Pin unpinned actions.
  • Prefer minimal/no third-party actions for sensitive auto-merge workflows.

7. Auto-merge workflow registration is clean (no ghost duplicates)

Verification:

gh api "repos/$OWNER/$REPO/actions/workflows" \
  --jq '.workflows[] | [.id,.name,.path,.state] | @tsv' | sort
gh workflow list --all

Pass criteria:

  • Exactly one active registration for the canonical auto-merge workflow path.
  • Legacy/stale registrations are disabled or removed.

Remediation:

  • Disable stale workflow IDs:
gh workflow disable <workflow_id>
  • Keep a single canonical filename on default branch.

8. Auto-merge workflow trigger and runtime behavior are reliable

Verification:

AUTO_WF="Enable PR Auto-Merge"  # adjust if needed
gh workflow view "$AUTO_WF" --yaml | sed -n '1,260p'
gh run list --workflow "$AUTO_WF" --limit 20 \
  --json databaseId,event,status,conclusion,headBranch,createdAt,url

Deep check for suspicious runs:

RUN_ID="<id>"
gh run view "$RUN_ID" --json event,conclusion,jobs
# Log text is critical for transient API failures (502/503/etc.)
gh run view "$RUN_ID" --log-failed | sed -n '1,260p'

Pass criteria:

  • Trigger is intentionally chosen (pull_request_target is often safer for base-branch-controlled orchestration; pull_request is valid when branch consistency is guaranteed).
  • Recent PR-event runs execute real jobs.
  • Workflow handles transient GitHub failures (retry/backoff or equivalent).
  • No repeating failures with push event + zero jobs + missing logs (ghost workflow symptom).

Remediation:

  • Move to a stable default-branch workflow file.
  • Use pull_request_target when orchestration must run from trusted base branch context.
  • Add retry/backoff around gh pr merge --auto ... to avoid one-off 5xx failures stranding PRs.
  • Disable stale workflow registrations.

9. Branch updater trigger coverage and run recency are reliable

Verification:

UPDATE_WF="Auto Update PR Branches"
gh workflow view "$UPDATE_WF" --yaml | sed -n '1,320p'
gh run list --workflow "$UPDATE_WF" --limit 30 \
  --json databaseId,event,status,conclusion,headBranch,headSha,createdAt,url

Correlate recent default-branch commits with updater runs:

git fetch origin "$DEFAULT_BRANCH"
git log --oneline -n 15 "origin/$DEFAULT_BRANCH"
# Check whether updater has runs for recent headSha values

Pass criteria:

  • Updater is not push-only in environments where merges are performed by automation/apps.
  • At least one fallback trigger exists (pull_request_target, schedule, or workflow_dispatch).
  • Updater runs continue to appear after recent merges to default branch.

Remediation:

  • Add fallback triggers to updater workflow (pull_request_target, schedule, workflow_dispatch).
  • Add concurrency guard to avoid overlapping updater runs.
  • Keep updater green when one PR cannot update, so other PRs still progress.

10. PR-level auto-merge state is actually mergeable (not just enabled)

Verification:

gh pr list --state open --limit 30 \
  --json number,title,isDraft,mergeStateStatus,autoMergeRequest \
  --jq '.[] | {number,title,isDraft,mergeStateStatus,autoMergeEnabled:(.autoMergeRequest != null)}'

Deep check:

PR_NUMBER="<pr_number>"
gh pr view "$PR_NUMBER" \
  --json autoMergeRequest,mergeStateStatus,isDraft,reviewDecision,statusCheckRollup

Pass criteria:

  • PRs intended to auto-merge show autoMergeEnabled: true.
  • Once checks and reviews are satisfied, mergeStateStatus is no longer BLOCKED/BEHIND for long periods.

Remediation:

  • If PRs stay BLOCKED with green checks, verify ruleset gates first:
    • required status checks exact name match
    • update rule/bypass compatibility
    • review requirements
  • If PRs stay BEHIND, verify branch updater trigger coverage and run recency.
  • Adjust ruleset/workflows, then re-test with a throwaway PR.

10a. Required checks are attached to the latest PR head SHA

This catches the incident pattern where a branch updater creates a new PR head commit, but ci only exists on the previous SHA, leaving auto-merge BLOCKED.

Verification:

PR_NUMBER="<pr_number>"
gh pr view "$PR_NUMBER" \
  --json headRefName,headRefOid,mergeStateStatus,statusCheckRollup

HEAD_SHA="$(gh pr view "$PR_NUMBER" --json headRefOid -q .headRefOid)"
gh api "repos/$OWNER/$REPO/commits/$HEAD_SHA/check-runs" \
  --jq '[.check_runs[] | {name,status,conclusion}]'

Pass criteria:

  • Every required status check context appears on the PR's current headRefOid.
  • If updater automation changed the head SHA, required checks (especially ci) are present or queued on that new SHA.

Remediation:

  • Immediate unstick:
HEAD_REF="$(gh pr view "$PR_NUMBER" --json headRefName -q .headRefName)"
gh workflow run CI --ref "$HEAD_REF"
  • Permanent fix:
    • Re-resolve headRefName + headRefOid after updater actions.
    • Verify required checks on that exact SHA.
    • Dispatch CI when required checks are missing.
    • Keep non-critical checks out of required-status contexts.

11. Cannot update this protected ref diagnostics for branch-update workflows

Verification:

gh workflow list --all
gh run list --workflow "Auto Update PR Branches" --limit 20 \
  --json databaseId,status,conclusion,event,headBranch,createdAt,url

Pass criteria:

  • Workflow updates eligible PR branches successfully.
  • Protected or fork branches are skipped/handled without failing the entire run.

Remediation:

  • In update-branch loops, continue on protected/fork failures.
  • Skip PR heads that are protected or not writable.
  • Treat this as per-PR conditional failure, not a global workflow failure.

12. Branch cleanup strategy is in place

Verification:

gh api "repos/$OWNER/$REPO" --jq '{delete_branch_on_merge}'
gh api "repos/$OWNER/$REPO/actions/workflows" \
  --jq '.workflows[] | {name,path,state}'

Pass criteria:

  • delete_branch_on_merge: true, or equivalent cleanup workflow exists and is active.

Remediation:

  • Enable delete-on-merge.
  • Add/repair cleanup workflow if additional cleanup behavior is required.

13. No stale branches from merged/closed PRs

Verification:

gh api "repos/$OWNER/$REPO/branches" --paginate --jq '.[].name' | sort -u > /tmp/live-branches.txt
gh pr list --state merged --limit 500 --json headRefName --jq '.[].headRefName' | sort -u > /tmp/merged-pr-branches.txt
gh pr list --state closed --limit 500 --json headRefName,mergedAt \
  --jq '.[] | select(.mergedAt==null) | .headRefName' | sort -u > /tmp/closed-pr-branches.txt
cat /tmp/merged-pr-branches.txt /tmp/closed-pr-branches.txt | sort -u > /tmp/candidate-stale-branches.txt
comm -12 /tmp/live-branches.txt /tmp/candidate-stale-branches.txt

Pass criteria:

  • Intersection output is empty, excluding intentional long-lived branches.

Remediation:

  • Delete confirmed stale branches:
git push origin --delete "<branch>"

14. Rulesets are primary policy and legacy protection is not conflicting

Verification:

gh api "repos/$OWNER/$REPO/rulesets" --jq 'length'
gh api "repos/$OWNER/$REPO/branches/$DEFAULT_BRANCH/protection" 2>/dev/null | jq '.'

Pass criteria:

  • Rulesets are the primary mechanism.
  • Legacy branch protection is absent or intentionally non-overlapping.
  • If branch protection endpoint returns 404 while rulesets exist, that is expected.

Remediation:

  • Consolidate policy into rulesets.
  • Remove redundant legacy branch protection after parity validation.

Optional Smoke Test (Recommended for Auto-Merge Incidents)

  1. Create a temporary PR from a throwaway branch.
  2. Enable auto-merge on the PR.
  3. Confirm auto-merge is enabled:
gh pr view <pr_number> --json autoMergeRequest,mergeStateStatus
  1. Confirm auto-merge workflow ran and executed jobs:
gh run list --workflow "Enable PR Auto-Merge" --limit 5
  1. Confirm branch updater can clear BEHIND without manual intervention.
  2. Confirm PR actually merges after checks pass (not just auto-merge enabled).
  3. Close/delete throwaway branch if still open.

Fast Unstick Playbook (Operational)

Use this only for incident response, then fix root cause in workflow/ruleset config.

# Re-enable auto-merge if missing
gh pr merge <pr_number> --auto --squash --repo "$OWNER_REPO"

# Force update a behind branch
gh api --method PUT "repos/$OWNER/$REPO/pulls/<pr_number>/update-branch" -f update_method=merge

# Ensure required CI exists on current head
HEAD_REF="$(gh pr view <pr_number> --json headRefName -q .headRefName)"
gh workflow run CI --ref "$HEAD_REF"

# Re-check status
gh pr view <pr_number> --repo "$OWNER_REPO" \
  --json headRefOid,mergeStateStatus,autoMergeRequest,statusCheckRollup

Tooling Notes (CLI Gotchas)

  • gh api does not support --repo; use full endpoint paths like repos/$OWNER/$REPO/....
  • In zsh, quote gh api endpoints that include ? query strings:
    • gh api 'repos/$OWNER/$REPO/actions/workflows/<id>/runs?per_page=20'

Report Format

## Branch Policy Audit Report
- Repository: OWNER/REPO
- Default branch: <branch>
- Timestamp (UTC): <iso8601>
- Overall status: PASS | NEEDS_ACTION | BLOCKED

### Findings
1. [SEV-<1-3>] <check name> - <pass/fail summary>
   Evidence: <key command output summary>
   Remediation: <next action>

### Actions Taken
1. <action performed or "none">

### Follow-up
1. <required human decision or "none">

Guardrails

  • Do not delete branches until confirmed stale and unprotected.
  • Do not disable workflows blindly; verify canonical registration first.
  • Default policy is no bypass actors on default-branch rulesets unless there is a documented, time-boxed incident exception.
  • Treat push + 0 jobs + no logs on workflow runs as likely ghost/stale registration evidence.
  • Do not treat autoMergeRequest != null as success by itself. Verify mergeStateStatus and actual merge outcome.
  • Prefer deterministic gh api evidence over assumptions.
  • If permissions are insufficient, report missing scope/permission and continue with remaining checks.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

book-manuscript-development

No summary provided by upstream source.

Repository SourceNeeds Review
General

creative-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

book-concept-strategy

No summary provided by upstream source.

Repository SourceNeeds Review
github-branch-policy | V50.AI