diff --git a/skills/github-project/references/actionlint-guide.md b/skills/github-project/references/actionlint-guide.md index 5164e93..af63ce0 100644 --- a/skills/github-project/references/actionlint-guide.md +++ b/skills/github-project/references/actionlint-guide.md @@ -459,3 +459,20 @@ Complex matrix expressions with `fromJSON()` may cause type-check warnings. Thes ignore: - 'fromJSON' ``` + +### yamllint `empty-lines` rejects trailing blank lines + +Unrelated to actionlint itself, but bites right next to it in CI: if you run `yamllint` in the same lane, its default `empty-lines` rule rejects a file that ends with more than one newline. Workflow files end with exactly one newline. + +Generators that use `echo "$CONTENT" > file.yml` frequently add a trailing blank; prefer: + +```bash +printf '%s\n' "$CONTENT" > file.yml +``` + +Verify after writing: + +```bash +# Bytes at end of file — should be `0a` (one newline), not `0a0a`. +tail -c 2 file.yml | xxd -p +``` diff --git a/skills/github-project/references/auto-merge-guide.md b/skills/github-project/references/auto-merge-guide.md index 27539fd..047f094 100644 --- a/skills/github-project/references/auto-merge-guide.md +++ b/skills/github-project/references/auto-merge-guide.md @@ -122,6 +122,48 @@ For each unresolved thread: **Re-sweep after follow-up PRs merge.** Copilot often reviews the follow-up PR itself and posts new threads. The sweep isn't one-shot — run it again until the count hits zero across all touched PRs. +## CI Annotations — Always Check Before Declaring a PR Clean + +CI checks can report `success` at the status-run level while still emitting **warning annotations** (typical for actionlint / shellcheck via reviewdog, CodeQL deprecation notices, YAML-lint). These annotations don't show up in `gh pr checks` or in the PR summary page — they only appear on the job's detail page or on the Files-Changed tab. Declaring a PR "clean" based on `gh pr checks` alone leaves real findings un-addressed. + +**Check explicitly:** + +```bash +# Annotations on a specific check run: +gh api repos/OWNER/REPO/check-runs/CHECK_RUN_ID/annotations --jq \ + '.[] | {message, annotation_level, path, start_line}' + +# All check runs for a commit that have any annotations: +gh api "repos/OWNER/REPO/commits/SHA/check-runs" --jq \ + '.check_runs[] | select(.output.annotations_count > 0) | + {name, annotations: .output.annotations_count}' +``` + +**Make warnings blocking.** reviewdog-based linters default to posting warnings that don't fail the workflow. Configure them to fail: + +```yaml +- uses: reviewdog/action-actionlint@v1 # or -shellcheck, -yamllint, etc. + with: + fail_level: error +``` + +`fail_level: error` is the modern input; the deprecated `fail_on_error` + `level` combination still works but is going away. When a new reviewdog-based linter is added, grep the caller for `fail_level:` and set it to `error` up front — otherwise real findings silently accumulate. + +## CI Re-runs Replay the Same Commit + +`gh run rerun ` **re-executes the ORIGINAL commit SHA**, not `HEAD`. If you push a fix and re-run a failed old workflow, the rerun still fails against the pre-fix code. + +**Right way:** push the fix, then either wait for the automatic run triggered by the push, or re-run the LATEST run: + +```bash +# Latest run ID for a workflow on a branch: +gh api "repos/OWNER/REPO/actions/runs?per_page=5" --jq \ + '.workflow_runs[] | select(.name == "CI") | {id, head_sha: .head_sha[:7]}' \ + | head -1 + +gh api repos/OWNER/REPO/actions/runs/RUN_ID/rerun -X POST +``` + ## Merge Queue Behavior and Pitfalls ### Sequential Processing diff --git a/skills/github-project/references/merge-strategy.md b/skills/github-project/references/merge-strategy.md index 31b6cb8..b8c344a 100644 --- a/skills/github-project/references/merge-strategy.md +++ b/skills/github-project/references/merge-strategy.md @@ -164,6 +164,26 @@ EOF **Fix:** Update auto-merge workflow to use `--merge` flag instead of `--rebase` or `--squash`. +### Rulesets cannot block merge on a pending review + +Neither branch protection nor rulesets support "block merge while any requested reviewer hasn't submitted yet". The options available are adjacent but not equivalent: + +| Setting | What it does | Not what you want | +|---|---|---| +| `required_approving_review_count: 1` | Needs **one approval** | Doesn't wait for other requested reviewers | +| `required_review_thread_resolution: true` | Blocks on **unresolved threads** | Doesn't block before any thread is created | + +If you need to hold merge until Copilot (or any other requested reviewer) has actually posted its review, the workaround is a custom GitHub Actions status check that queries pending reviewers and fails if any are outstanding — then require that check in branch protection. + +```bash +# Example: fail the check if any reviewer is still requested. +pending=$(gh api "repos/$REPO/pulls/$PR" --jq '.requested_reviewers | length') +if [[ "$pending" -gt 0 ]]; then + echo "::error::Still waiting on $pending requested reviewer(s)" + exit 1 +fi +``` + ## References - [GitHub Branch Protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches) diff --git a/skills/github-project/references/multi-repo-operations.md b/skills/github-project/references/multi-repo-operations.md index 3e13e33..4bc754c 100644 --- a/skills/github-project/references/multi-repo-operations.md +++ b/skills/github-project/references/multi-repo-operations.md @@ -26,6 +26,40 @@ name specific repos to skip.) Wait for "go" (or an equivalent affirmative). Silence is not approval. +## Pre-Flight Per-Repo Checks + +Before touching each repo in the batch, check three things the dry-run manifest won't catch — they tend to surface as 30 silent failures across a fleet loop: + +### 1. Default branch name + +Not every repo uses `main`. Some legacy repos are on `master`; forks can be on anything. + +```bash +DEFAULT_BRANCH=$(gh api "repos/$REPO" --jq '.default_branch') +``` + +Use `$DEFAULT_BRANCH` everywhere a script would otherwise hard-code `main`. Pushing to the wrong branch either silently creates a new branch or fails with a confusing rejection. + +### 2. Archived repos + +Archived repos reject most writes with a generic permission error. Dependabot/Renovate sometimes still open PRs on them (via the pre-archive config), and a batch loop that tries to merge them fails cryptically. + +```bash +ARCHIVED=$(gh api "repos/$REPO" --jq '.archived') +if [[ "$ARCHIVED" == "true" ]]; then + # Skip, or handle specially: unarchive → close PR → re-archive. + continue +fi +``` + +Never enable auto-merge on archived repos — the auto-merge plumbing fails at set-up time with "archived" errors. + +### 3. Contents API vs branch protection + +`gh api -X PUT repos/.../contents/...` is the fastest path for tiny single-file edits across a fleet — but it returns HTTP 409 on any repo that requires PRs, a merge queue, or signed commits. If your batch mixes repos with and without branch protection, this path breaks mid-loop and leaves half the fleet updated. + +**Safer default for batch file edits**: open a one-commit PR per repo even when the Contents API would work. Gives you a reviewable diff, matches any future signing/protection rule tightening, and keeps behavior consistent across the fleet. + ## Parallel PR Rebasing For N PRs that need rebasing on their default branches, dispatch parallel subagents — one per PR — with failure isolation.