Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions .github/workflows/label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,31 @@ name: Auto-label PR
# current changed-file set, so removing a change also removes its
# label.
#
# Stays on `pull_request` for now — same chicken-and-egg as
# pr-title.yml (see its header comment). Fork PRs won't auto-label
# until we do the dual-trigger transition tracked in #56.
# Dual-trigger: `pull_request` runs from the PR head branch (works
# on internal PRs); `pull_request_target` runs from the default
# branch with base-repo permissions (so fork PRs also get labels).
# `actions/labeler` is idempotent — same label set applied twice is
# a no-op, so the duplicate run on internal PRs is harmless.
#
# A follow-up PR can drop the `pull_request` trigger once we've
# confirmed `pull_request_target` is firing correctly. Closes #56.

on:
pull_request:
types: [opened, synchronize, reopened]
pull_request_target:
types: [opened, synchronize, reopened]
Comment thread
EricAndrechek marked this conversation as resolved.

permissions:
contents: read
pull-requests: write

concurrency:
group: label-${{ github.event.pull_request.number }}
# Same reasoning as pr-title.yml: include event_name so
# `pull_request` and `pull_request_target` don't cancel each
# other (which on fork PRs could kill the only run with
# permissions to apply labels).
group: label-${{ github.event.pull_request.number }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
Expand Down
35 changes: 24 additions & 11 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,41 @@ name: Lint PR title
# Required by the `main branch protection` ruleset as the `Validate`
# check — must report on every PR or merge gets stuck at "pending."
#
# NOTE: stays on `pull_request` (not `pull_request_target`) for now
# because switching the trigger in a PR creates a coverage gap — on
# `pull_request` events GitHub evaluates the workflow from the PR
# head (which would already say `pull_request_target`, so no match),
# and `pull_request_target` fires from the default branch's file
# (which would still say `pull_request`, also no match). See #56 for
# the plan (dual-trigger transition, then drop `pull_request`).
# Dual-trigger: `pull_request` runs from the PR head branch (works
# on internal PRs with full permissions for the sticky comment);
# `pull_request_target` runs from the default branch with base-repo
# permissions (so fork PRs also get the sticky comment). Both fire
# on internal PRs — the second run is a harmless duplicate because
# the comment marker and title-check logic are idempotent.
#
# Fork PRs under `pull_request` see a read-only GITHUB_TOKEN, so the
# sticky comment silently skips there — the Validate check's red X
# is still visible, just not the helpful fix-it comment.
# A follow-up PR can drop the `pull_request` trigger once we've
# confirmed `pull_request_target` is firing correctly in production.
# Closes #56 (the dual-trigger transition).

on:
pull_request:
types: [opened, edited, reopened, synchronize]
pull_request_target:
types: [opened, edited, reopened, synchronize]
Comment thread
EricAndrechek marked this conversation as resolved.

permissions:
# `issues: write` is required for /repos/.../issues/{num}/comments
# (the sticky-comment endpoint) — PRs are issues at the REST layer
# for comments but `pull-requests: write` alone does NOT grant that
# scope. Without it, fork PR runs fail to post the comment even on
# `pull_request_target` with full base-repo permissions.
contents: read
Comment thread
EricAndrechek marked this conversation as resolved.
issues: write
pull-requests: write

concurrency:
group: pr-title-${{ github.event.pull_request.number }}
# Include event_name in the group so `pull_request` and
# `pull_request_target` don't cancel each other. Copilot flagged
# the original single-key group would let the first-to-start run
# get cancelled by the second, which on fork PRs could kill the
# only run (pull_request_target) with permissions to post the
# sticky comment.
group: pr-title-${{ github.event.pull_request.number }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
Expand Down
20 changes: 15 additions & 5 deletions .github/workflows/project-orchestrator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,22 @@ name: Project orchestrator
# and fresh forks don't produce failing runs.

on:
# Note: despite being documented in GitHub's event-list docs,
# `pull_request_review_thread` is rejected by the Actions parser
# (observed 2026-04-22 on this repo). Including it marks the whole
# workflow as "Invalid workflow file" and suppresses ALL triggers.
# Caveat Copilot flagged: when a reviewer ONLY resolves threads
# without any push or new check_suite following, orchestration
# won't re-eval immediately. In practice the next push from the
# coder (addressing feedback) or check_suite (CI completion from
# that push) always follows thread resolution closely, so the
# gap is small. If it becomes a real pain point, consider adding
# a `schedule:` cron re-eval or a `workflow_dispatch:` manual
# re-eval path.
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, review_requested]
pull_request_review:
types: [submitted, dismissed]
pull_request_review_thread:
types: [resolved, unresolved]
check_suite:
types: [completed]

Expand Down Expand Up @@ -103,7 +113,7 @@ jobs:
EVENT_NAME: ${{ github.event_name }}
run: |
case "$EVENT_NAME" in
pull_request|pull_request_target|pull_request_review|pull_request_review_thread)
pull_request_target|pull_request_review)
num="${{ github.event.pull_request.number }}"
;;
check_suite)
Expand Down Expand Up @@ -162,7 +172,7 @@ jobs:
# A review was dismissed — re-evaluate so the board
# reflects the invalidated-approval state.
action="reeval"
elif [[ ( "$EVENT_NAME" == "pull_request" || "$EVENT_NAME" == "pull_request_target" ) && "$EVENT_ACTION" == "review_requested" ]]; then
elif [[ "$EVENT_NAME" == "pull_request_target" && "$EVENT_ACTION" == "review_requested" ]]; then
# `review_requested` fires on every reviewer-add, including
# the initial one when the PR opens with pre-populated
# reviewers. Copilot flagged that sender == author alone
Expand Down Expand Up @@ -190,7 +200,7 @@ jobs:
action="none"
fi
fi
elif [[ "$EVENT_NAME" == "pull_request" || "$EVENT_NAME" == "pull_request_target" || "$EVENT_NAME" == "pull_request_review_thread" || "$EVENT_NAME" == "check_suite" ]]; then
elif [[ "$EVENT_NAME" == "pull_request_target" || "$EVENT_NAME" == "check_suite" ]]; then
action="reeval"
fi

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ Dev tools (`gotestsum`, `gofumpt`, `goimports`) are pinned in `go.mod` via nativ
| Claude (`.github/workflows/claude-review.yml`) | Our workflow, `pull_request: synchronize` trigger | Yes, auto — **updates the same sticky comment** rather than posting new ones | No (advisory) unless added to required checks |
| Gemini Code Assist | Marketplace App at repo level | Yes on synchronize, **but silently skips `.github/workflows/**`** (built-in exclusion, can't be overridden) — so Gemini reviews rarely see infra PRs | No (advisory) |
| Copilot | GitHub-native when reviewer has Copilot Pro enabled | Yes if enabled in Copilot settings | No (advisory) |
| Human admins (Eric / Taite) | Auto-assigned to the **PR** (as the `assignees` field, not a review request) by `.github/workflows/project-orchestrator.yml` **only once** the PR is bot-clean: required checks green AND all review threads resolved. Draft PRs get flipped to ready at the same moment, PR card goes on Task Board (project #7) with Status=Ready. Assignment logic: PR author == Eric → assign Taite; author == Taite → assign Eric; other authors → assign both. The Task Board card + assignee are the single signal — GitHub's native review-request channel is intentionally NOT used so notifications don't fire mid-iteration. | Workflow re-checks on every push (`pull_request_target`), review (`pull_request_review`), thread resolution (`pull_request_review_thread`), and check completion (`check_suite: completed`). Idempotent across re-fires. | Yes — `.github/workflows/admin-approval.yml` is a required status check that fails unless Eric or Taite has an `APPROVED` review (Dependabot PRs bypass). |
| Human admins (Eric / Taite) | Auto-assigned to the **PR** (as the `assignees` field, not a review request) by `.github/workflows/project-orchestrator.yml` **only once** the PR is bot-clean: required checks green AND all review threads resolved. Draft PRs get flipped to ready at the same moment, PR card goes on Task Board (project #7) with Status=Ready. Assignment logic: PR author == Eric → assign Taite; author == Taite → assign Eric; other authors → assign both. The Task Board card + assignee are the single signal — GitHub's native review-request channel is intentionally NOT used so notifications don't fire mid-iteration. | Workflow re-checks on every push (`pull_request_target`), review (`pull_request_review`), and check completion (`check_suite: completed`). Idempotent across re-fires. Note: `pull_request_review_thread` would be the natural trigger for thread-resolution events but GitHub Actions' parser rejects it; re-evaluation after thread resolution happens implicitly via the next push or check_suite event. | Yes — `.github/workflows/admin-approval.yml` is a required status check that fails unless Eric or Taite has an `APPROVED` review (Dependabot PRs bypass). |

> **Known limitation**: Gemini Code Assist silently ignores all files under `.github/workflows/**` — a hardcoded Google default that `.gemini/config.yaml`'s `ignore_patterns` can't remove. For workflow-heavy PRs, Claude review is the primary AI reviewer. Gemini still covers `CHANGELOG.md`, docs, source code, and configuration outside `.github/`.

Expand Down
Loading