From bf3fe81b3e129a107618cc0ffc616eddb90f55a4 Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Wed, 22 Apr 2026 10:27:57 +0300 Subject: [PATCH 1/2] @-mention non-collaborator release contributors; widen toolhive docs paths Two independent but related improvements to the upstream-release-docs pipeline: 1. Reviewer routing: @-mention contributors we can't auto-assign The existing reviewer-extract step batched all non-bot release commit authors and filtered to docs-website collaborators. On PR #759 that filter silently dropped four of five candidates (`reyortiz3`, `ChrisJBurns`, `jhroz`, `tgrunnag` are all upstream toolhive contributors but not collaborators on docs-website). The filter is mandatory because `gh pr edit --add-reviewer` returns 422 for any non-collaborator in the list, dropping the valid reviewers alongside the invalid ones. Now split the candidates: - ASSIGN_LIST (collaborators): batch-assigned as reviewers via `--add-reviewer`. Unchanged behavior. - MENTION_LIST (everyone else): @-mentioned in a new "Release contributors" section of the PR body so they see the PR documenting their work even though we can't request their review via the API. Detecting Stacklok employees directly (e.g. via the `stackers` team) requires a PAT with read:org scope -- `GITHUB_TOKEN` doesn't have it, and public-org-membership checks only see members with public membership. Deferred; noted in a comment. 2. Expand toolhive docs_paths hints The `toolhive` upstream is a monorepo shipping the CLI, the Kubernetes operator, and the vMCP gateway, plus cross-cutting features that land in concepts/, integrations/, tutorials/, and hand-written reference pages. The previous hints listed only the three guides-* folders. In practice the skill's Phase 3 impact map expanded beyond the hints (PR #759 touched concepts/, integrations/, tutorials/), so this is a focus improvement rather than a bug fix -- more accurate hints let Phase 2's source reading home in on the right areas without re-scanning unrelated docs. Other projects' hints are already accurate for their scope. toolhive-cloud-ui intentionally keeps docs_paths: [] -- no associated docs in this repo yet. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/upstream-projects.yaml | 11 ++++ .github/workflows/upstream-release-docs.yml | 71 ++++++++++++++++----- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml index a4d36fd4..4fcf9cf0 100644 --- a/.github/upstream-projects.yaml +++ b/.github/upstream-projects.yaml @@ -36,10 +36,21 @@ projects: - id: toolhive repo: stacklok/toolhive version: v0.23.1 + # toolhive is a monorepo covering the CLI, the Kubernetes + # operator, and the vMCP gateway. It also introduces cross- + # cutting features that land in concepts/, integrations/, + # tutorials/, and hand-written reference pages. These entries + # are hints, not constraints -- the skill's Phase 3 impact map + # decides what actually changes. Hints focus Phase 2's source + # reading so the skill doesn't re-scan unrelated areas. docs_paths: - docs/toolhive/guides-cli - docs/toolhive/guides-k8s - docs/toolhive/guides-vmcp + - docs/toolhive/concepts + - docs/toolhive/integrations + - docs/toolhive/tutorials + - docs/toolhive/reference/authz-policy-reference.mdx assets: - release_asset: swagger.yaml destination: static/api-specs/toolhive-api.yaml diff --git a/.github/workflows/upstream-release-docs.yml b/.github/workflows/upstream-release-docs.yml index e27b789e..9550db2b 100644 --- a/.github/workflows/upstream-release-docs.yml +++ b/.github/workflows/upstream-release-docs.yml @@ -391,7 +391,7 @@ jobs: id: pre_skill run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - name: Extract reviewers from release compare + - name: Extract reviewers and contributors from release compare id: reviewers env: REPO: ${{ steps.detect.outputs.repo }} @@ -408,29 +408,54 @@ jobs: echo "compare_ok=false" >> "$GITHUB_OUTPUT" fi - # Filter out bot accounts, then further filter to only this - # repo's collaborators. GitHub rejects reviewer requests for - # non-collaborators with 422, which would fail the whole - # `gh pr edit --add-reviewer` call and drop the valid - # reviewers along with the invalid ones. Community - # contributors from the upstream repo often aren't - # collaborators here; silently skip them. + # Filter out bot accounts. CANDIDATES=$(echo "$COMPARE" | grep -Ev '(\[bot\]$|^github-actions|^stacklokbot$|^dependabot|^renovate|^copilot)' || true) - REVIEWERS="" + # Split candidates into two groups: + # - ASSIGN_LIST: collaborators on this repo -- safe to pass + # to `gh pr edit --add-reviewer`. GitHub rejects the + # entire call with 422 if any name in the list isn't a + # collaborator, so the filter is mandatory to avoid + # dropping valid reviewers alongside invalid ones. + # - MENTION_LIST: everyone else. Upstream contributors + # (often Stacklok employees with private org membership + # we can't detect via GITHUB_TOKEN, which lacks + # read:org) still deserve a ping so they see the PR + # documenting their work. @-mentioning in the PR body + # handles that without needing reviewer-assignment + # rights. + # Future: a PAT with read:org could let us also check + # membership in the `stackers` team and promote those to + # ASSIGN_LIST. Deferred -- not wiring a secret in now. + ASSIGN_LIST="" + MENTION_LIST="" while IFS= read -r login; do [ -z "$login" ] && continue if gh api "repos/$REVIEW_REPO/collaborators/$login" --silent 2>/dev/null; then - REVIEWERS="${REVIEWERS:+$REVIEWERS,}$login" + ASSIGN_LIST="${ASSIGN_LIST:+$ASSIGN_LIST,}$login" + else + MENTION_LIST="${MENTION_LIST:+$MENTION_LIST }@$login" fi done <<< "$CANDIDATES" - # Cap at 5 to avoid review fatigue. - REVIEWERS=$(echo "$REVIEWERS" | tr ',' '\n' | head -5 | paste -sd, -) + # Cap auto-assignments at 5 to avoid review fatigue. Mentions + # aren't capped -- they're cheap and the block is a single + # PR-body notification. + ASSIGN_LIST=$(echo "$ASSIGN_LIST" | tr ',' '\n' | head -5 | paste -sd, -) - echo "list=$REVIEWERS" >> "$GITHUB_OUTPUT" - echo "Reviewers: ${REVIEWERS:-}" + echo "list=$ASSIGN_LIST" >> "$GITHUB_OUTPUT" + { + echo "mention_block<> "$GITHUB_OUTPUT" + echo "Auto-assigned: ${ASSIGN_LIST:-}" + echo "Mentioned: ${MENTION_LIST:-}" - name: Read docs_paths hint id: hints @@ -797,6 +822,8 @@ jobs: GAPS_BLOCK: ${{ steps.signals.outputs.gaps_block }} AUTOGEN_NOTE: ${{ steps.autogen.outputs.note }} COMPARE_OK: ${{ steps.reviewers.outputs.compare_ok }} + MENTION_BLOCK: ${{ steps.reviewers.outputs.mention_block }} + ASSIGN_LIST: ${{ steps.reviewers.outputs.list }} run: | START='' END='' @@ -830,8 +857,22 @@ jobs: echo "$GAPS_BLOCK" echo "" fi - echo "Reviewers below are non-bot commit authors in the release range who are also collaborators on this repo." + echo "### Release contributors" echo "" + if [ -n "$ASSIGN_LIST" ]; then + # Comma list -> @-mention list for rendering. + ASSIGNED_MENTIONS=$(echo "$ASSIGN_LIST" | tr ',' '\n' | sed 's/^/@/' | paste -sd' ' -) + echo "Auto-assigned as reviewers (collaborators on this repo): $ASSIGNED_MENTIONS" + echo "" + fi + if [ -n "$MENTION_BLOCK" ]; then + echo "$MENTION_BLOCK" + echo "" + fi + if [ -z "$ASSIGN_LIST" ] && [ -z "$MENTION_BLOCK" ]; then + echo "No non-bot contributors were found in the release range." + echo "" + fi echo "$END" } > /tmp/section.md From 3b5eac0d9600e977145a66fbdefb23ce54d3475b Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Wed, 22 Apr 2026 10:37:52 +0300 Subject: [PATCH 2/2] Assign reviewers per-user instead of pre-filtering by collaborator API Replaces the upfront `gh api repos/X/collaborators/Y` filter with per-user `gh pr edit --add-reviewer` attempts. Routes GitHub's own rejection (422) into the mention list instead of relying on our filter to pre-compute the split. Why: on PR #759 the collaborator check returned 404 for Stacklok employees who ARE collaborators via the `stackers` team (push permission on docs-website) -- `ChrisJBurns`, `jhrozek`, `reyortiz3`, `tgrunnagle`. Only `rdimitrov` passed, so four reviewers silently vanished. A local check with a PAT that has read:org confirms all five are collaborators; the discrepancy appears to be GITHUB_TOKEN treating team-based access differently at the collaborator endpoint, though we haven't pinned the exact rule. The authoritative answer is "will GitHub accept this person as a reviewer right now" -- asking that question directly (via the add-reviewer API) avoids the filter being wrong. Each attempt is independent, so one rejection doesn't kill the batch. The separate "Add reviewers" step is now redundant -- assignments happen inline during candidate iteration. Dropped. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/upstream-release-docs.yml | 66 +++++++++++---------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/.github/workflows/upstream-release-docs.yml b/.github/workflows/upstream-release-docs.yml index 9550db2b..8e84f286 100644 --- a/.github/workflows/upstream-release-docs.yml +++ b/.github/workflows/upstream-release-docs.yml @@ -391,13 +391,14 @@ jobs: id: pre_skill run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" - - name: Extract reviewers and contributors from release compare + - name: Assign reviewers and prepare contributor mentions id: reviewers env: REPO: ${{ steps.detect.outputs.repo }} PREV: ${{ steps.detect.outputs.prev_tag }} NEW: ${{ steps.detect.outputs.new_tag }} REVIEW_REPO: ${{ github.repository }} + PR_NUMBER: ${{ steps.eff.outputs.number }} run: | # Get non-bot commit authors in the release range. if COMPARE=$(gh api "repos/$REPO/compare/$PREV...$NEW" \ @@ -412,43 +413,53 @@ jobs: CANDIDATES=$(echo "$COMPARE" | grep -Ev '(\[bot\]$|^github-actions|^stacklokbot$|^dependabot|^renovate|^copilot)' || true) - # Split candidates into two groups: - # - ASSIGN_LIST: collaborators on this repo -- safe to pass - # to `gh pr edit --add-reviewer`. GitHub rejects the - # entire call with 422 if any name in the list isn't a - # collaborator, so the filter is mandatory to avoid - # dropping valid reviewers alongside invalid ones. - # - MENTION_LIST: everyone else. Upstream contributors - # (often Stacklok employees with private org membership - # we can't detect via GITHUB_TOKEN, which lacks - # read:org) still deserve a ping so they see the PR - # documenting their work. @-mentioning in the PR body - # handles that without needing reviewer-assignment - # rights. - # Future: a PAT with read:org could let us also check - # membership in the `stackers` team and promote those to - # ASSIGN_LIST. Deferred -- not wiring a secret in now. + # Attempt to assign each candidate as a reviewer individually, + # rather than filtering upfront and batching. Rationale: + # - `gh pr edit --add-reviewer "a,b,c"` is atomic. A single + # 422 on any name aborts the whole call, dropping valid + # names alongside invalid ones. + # - `gh api repos/X/collaborators/Y` as a pre-filter is + # unreliable from a GITHUB_TOKEN in Actions: on PR #759 + # the check returned 404 for Stacklok employees who ARE + # collaborators via the `stackers` team (push perm on + # this repo), and only `rdimitrov` slipped through. We + # suspect the collaborator endpoint treats team-based + # access differently for GITHUB_TOKEN vs PATs with + # read:org, but haven't nailed down the exact rule. + # Per-user attempts sidestep both issues: the authoritative + # answer is "does GitHub accept this as a reviewer right now" + # and we ask the API that question directly. + # + # Cap attempts at 5 to avoid review fatigue on big releases. + TRIED=0 ASSIGN_LIST="" MENTION_LIST="" while IFS= read -r login; do [ -z "$login" ] && continue - if gh api "repos/$REVIEW_REPO/collaborators/$login" --silent 2>/dev/null; then + if [ "$TRIED" -ge 5 ]; then + # Over the review-fatigue cap -- mention any additional + # contributors instead of trying to assign them. + MENTION_LIST="${MENTION_LIST:+$MENTION_LIST }@$login" + continue + fi + TRIED=$((TRIED + 1)) + if gh pr edit "$PR_NUMBER" --add-reviewer "$login" 2>/dev/null; then ASSIGN_LIST="${ASSIGN_LIST:+$ASSIGN_LIST,}$login" + echo "Assigned: $login" else MENTION_LIST="${MENTION_LIST:+$MENTION_LIST }@$login" + echo "Mention (assignment rejected by GitHub): $login" fi done <<< "$CANDIDATES" - # Cap auto-assignments at 5 to avoid review fatigue. Mentions - # aren't capped -- they're cheap and the block is a single - # PR-body notification. - ASSIGN_LIST=$(echo "$ASSIGN_LIST" | tr ',' '\n' | head -5 | paste -sd, -) - + # Exposed for diagnostic visibility in the PR body (e.g., + # "Auto-assigned: @alice @bob") and for the next workflow_ + # dispatch retry to know what was attempted. echo "list=$ASSIGN_LIST" >> "$GITHUB_OUTPUT" { echo "mention_block<