ci(automation): drop GraphQL from assign-linked-issue-author#5166
ci(automation): drop GraphQL from assign-linked-issue-author#5166jyaunches wants to merge 1 commit into
Conversation
Replaces the GraphQL `closingIssuesReferences` field query with REST +
PR-body parsing for closing-keyword discovery, eliminating the workflow's
dependency on a GitHub endpoint that periodically 401s on
`pull_request_target` runs.
Symptom that motivated this:
On 2026-06-10, GitHub's GraphQL endpoint started returning HTTP 401
"Requires authentication" intermittently for this workflow's
`gh pr view --json closingIssuesReferences,...` calls. Within a single
65-minute window (15:08–16:13 UTC), 8 runs across 6 distinct branches
failed identically — including the scheduled run on `main`. The
underlying token and permissions are valid; the endpoint itself was
flaking. Reruns also failed on the same instance for hours.
This workflow is non-required (not in branch-protection's required-checks
set), so the cosmetic red on every affected open PR was non-blocking,
but it was visible noise on every PR opened that day and it broke the
intended "auto-assign issue authors" loop for any PR that landed during
the bad window.
Why drop GraphQL entirely instead of just adding retry/backoff:
- The workflow's only need from GraphQL is the `closingIssuesReferences`
computed field. The same outcome is reachable from REST + parsing the
PR body for GitHub's documented closing keywords.
- Removing the dependency removes the failure mode altogether, rather
than reducing its probability.
- REST endpoints used by this workflow (`/repos/{owner}/{repo}/pulls/{N}`,
`/repos/{owner}/{repo}/issues/{N}/assignees/{user}`,
`POST /repos/{owner}/{repo}/issues/{N}/assignees`) have not exhibited
the same flake.
Changes:
- `gh pr view --json closingIssuesReferences,...` → `gh api repos/{owner}/
{repo}/pulls/{N}` (REST) + Python body parser for closing keywords.
- `gh pr list --json closingIssuesReferences,...` → `gh api --paginate
repos/{owner}/{repo}/pulls?state=open` (REST).
- `gh issue edit --add-assignee` → `gh api -X POST repos/{owner}/{repo}/
issues/{N}/assignees -f assignees[]=login` (REST; `gh issue edit` uses
GraphQL internally).
- Body parser handles GitHub's closing keywords (close/closes/closed,
fix/fixes/fixed, resolve/resolves/resolved) and same-repo reference
forms (`#N`, `OWNER/REPO#N`, `GH-N`, GitHub issue URLs). Ignores
fenced code blocks and HTML comments. Cross-repo refs are excluded
because this workflow can only assign authors to issues in its own
repo (matches prior behavior — `gh issue edit` would have failed for
cross-repo refs returned by GraphQL).
Trade-off acknowledged:
Body-parsing does not pick up linked issues added solely via the PR
sidebar's "Development" picker without a corresponding closing keyword
in the body. That edge case has not been observed for this workflow's
job scope; PR templates and contributor norms in this repo use
explicit `Closes #N` / `Refs #N` lines.
Verification:
- YAML syntax check: `python3 -c "import yaml; yaml.safe_load(...)"` OK
- Parser unit-tested in isolation against 20 cases including:
all 9 closing keywords (close, closes, closed, fix, fixes, fixed,
resolve, resolves, resolved), `Closes:` colon variant, mixed case,
multiple keywords per body, dedup, same-repo qualified refs,
cross-repo exclusion, GH-N form, full-URL form, fenced-code-block
exclusion, HTML-comment exclusion, "fixed in #N" non-match (matches
GitHub's parser behavior), empty body, null body. 20/20 pass.
- Manual end-to-end check: parsed PR #5119's body (`Closes #5113`) →
returns `['5113']`. Parsed PR #5153's body (`Refs #N` only, no
closing keywords) → returns `[]`.
📝 WalkthroughWalkthroughThis pull request migrates the GitHub Actions issue assignment workflow from GraphQL queries and high-level ChangesGitHub Actions Issue Assignment Workflow Migration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
E2E Advisor RecommendationRequired E2E: None Full advisor summaryE2E Recommendation AdvisorBase: Required E2E
Optional E2E
New E2E recommendations
|
E2E Scenario Advisor RecommendationRequired scenario E2E: None Full scenario advisor summaryE2E Scenario AdvisorBase: Required scenario E2E
Optional scenario E2E
Relevant changed files
|
PR Review AdvisorFindings: 1 needs attention, 2 worth checking, 0 nice ideas Review findings🛠️ Needs attention
🔎 Worth checking
🌱 Nice ideas
Consider writing more tests for
This is an automated advisory review. A human maintainer must make the final merge decision. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/assign-linked-issue-author.yaml:
- Around line 85-88: The current regex built into pattern (pattern =
re.compile(r"\b" + keyword + r"\b\s*:?\s*" + ref, flags=re.IGNORECASE)) only
captures one issue ref after the keyword; update the logic so it collects
subsequent refs separated by commas or the word "and". Modify the compiled regex
(or add a follow-up findall) to allow repeated occurrences of ref after the
initial match (for example make the part after keyword accept one ref followed
by zero-or-more groups like (?:\s*(?:,|and)\s*ref) or run a second re.findall
using the ref pattern on the substring following the keyword match) and then
iterate those matches to assign each issue; refer to the existing variables
pattern, keyword and ref and update the code that extracts matches so all refs
like "Closes `#1`, `#2` and `#3`" are collected.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 32114935-3b0d-4afc-b937-7637025de0b4
📒 Files selected for processing (1)
.github/workflows/assign-linked-issue-author.yaml
| pattern = re.compile( | ||
| r"\b" + keyword + r"\b\s*:?\s*" + ref, | ||
| flags=re.IGNORECASE, | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if any existing PR bodies use comma-separated closing references
gh api --paginate "repos/$GITHUB_REPOSITORY/pulls?state=all&per_page=100" \
--jq '.[] | select(.body != null) | {number, body}' 2>/dev/null | \
grep -iE '(close[sd]?|fix(e[sd])?|resolve[sd]?)\s*#[0-9]+\s*,\s*#[0-9]+' | head -5Repository: NVIDIA/NemoClaw
Length of output: 41
🏁 Script executed:
#!/bin/bash
# Identify whether any PR bodies in this repo contain comma- or "and"-separated issue lists
set -euo pipefail
REPO="${GITHUB_REPOSITORY:-NVIDIA/NemoClaw}"
echo "Repo: $REPO" >&2
# Comma-separated examples: "Closes `#1`, `#2`" or "Fixes `#1`, `#2`"
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '.[] | select(.body != null) | {number, body}' 2>/dev/null | \
rg -i --no-heading '(close[sd]?|fix(e[sd])?|resolve[sd]?)\s*#\d+\s*,\s*#\d+' --max-count 5
# "and"-separated examples: "Fixes `#1` and `#2`"
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '.[] | select(.body != null) | {number, body}' 2>/dev/null | \
rg -i --no-heading '(close[sd]?|fix(e[sd])?|resolve[sd]?)\s*#\d+\s+(and|&)\s*#\d+' --max-count 5
# Also get rough counts for each pattern (without printing bodies)
echo "Counts (comma-separated / and-separated):" >&2
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '[.[] | select(.body != null) | select((.body|test("(?i)(close[sd]?|fix(e[sd])?|resolve[sd]?)\\\\s*#\\\\d+\\\\s*,\\\\s*#\\\\d+"))) ] | length' 2>/dev/null
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '[.[] | select(.body != null) | select((.body|test("(?i)(close[sd]?|fix(e[sd])?|resolve[sd]?)\\\\s*#\\\\d+\\\\s+(and|&|\\\\band\\\\b)\\\\s*#\\\\d+"))) ] | length' 2>/dev/nullRepository: NVIDIA/NemoClaw
Length of output: 8400
🏁 Script executed:
#!/bin/bash
# Identify whether any PR bodies in this repo contain comma- or "and"-separated issue lists
set -euo pipefail
REPO="${GITHUB_REPOSITORY:-NVIDIA/NemoClaw}"
echo "Repo: $REPO" >&2
comma_regex='(close[sd]?|fix(e[sd])?|resolve[sd]?)\s*#[0-9]+\s*,\s*#[0-9]+'
and_regex='(close[sd]?|fix(e[sd])?|resolve[sd]?)\s*#[0-9]+\s+(and|&)\s*#[0-9]+'
# Print up to 5 matching PR numbers/bodies (compact)
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '.[] | select(.body != null) | {number, body}' 2>/dev/null | \
grep -iE "$comma_regex" | head -5
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '.[] | select(.body != null) | {number, body}' 2>/dev/null | \
grep -iE "$and_regex" | head -5
# Get rough counts (still limited to what the API returns within paginate)
echo "Counts:" >&2
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '[.[] | select(.body != null) | select((.body | test($comma_re; "i")))] | length' 2>/dev/null --arg comma_re "$comma_regex"
gh api --paginate "repos/$REPO/pulls?state=all&per_page=100" \
--jq '[.[] | select(.body != null) | select((.body | test($and_re; "i")))] | length' 2>/dev/null --arg and_re "$and_regex"Repository: NVIDIA/NemoClaw
Length of output: 8400
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/assign-linked-issue-author.yaml"
echo "== $FILE =="
nl -ba "$FILE" | sed -n '1,200p'Repository: NVIDIA/NemoClaw
Length of output: 176
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/workflows/assign-linked-issue-author.yaml"
echo "== $FILE =="
# cat -n is commonly available; fall back to awk if needed
if command -v cat >/dev/null 2>&1 && command -v sed >/dev/null 2>&1; then
cat -n "$FILE" | sed -n '1,220p'
else
awk '{printf "%6d\t%s\n", NR, $0}' "$FILE" | sed -n '1,220p'
fiRepository: NVIDIA/NemoClaw
Length of output: 7851
Fix linked-issue parsing for multi-issue lists (“Closes #1, #2” / “Fixes #1 and #2”)
.github/workflows/assign-linked-issue-author.yaml builds pattern = re.compile(r"\b" + keyword + r"\b\s*:?\s*" + ref, flags=re.IGNORECASE), which matches only a single keyword + one issue ref per match. For bodies like Closes #3562, #3568 / `Fixes `#1` and `#2, only the first #N gets captured, so later issues won’t be assigned to the PR author.
Extend the regex/parsing to collect additional #N refs after the initial keyword when separated by commas/“and”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/assign-linked-issue-author.yaml around lines 85 - 88, The
current regex built into pattern (pattern = re.compile(r"\b" + keyword +
r"\b\s*:?\s*" + ref, flags=re.IGNORECASE)) only captures one issue ref after the
keyword; update the logic so it collects subsequent refs separated by commas or
the word "and". Modify the compiled regex (or add a follow-up findall) to allow
repeated occurrences of ref after the initial match (for example make the part
after keyword accept one ref followed by zero-or-more groups like
(?:\s*(?:,|and)\s*ref) or run a second re.findall using the ref pattern on the
substring following the keyword match) and then iterate those matches to assign
each issue; refer to the existing variables pattern, keyword and ref and update
the code that extracts matches so all refs like "Closes `#1`, `#2` and `#3`" are
collected.
|
Thanks for jumping on the CI noise here. After looking at the run history, this appears most likely to have been a transient GitHub API/auth failure rather than a durable problem in this workflow or its permissions. The workflow has since recovered, and because this check is non-required, I don't think we should carry a custom REST/body-parser workaround for a temporary upstream outage. If this starts recurring, I'd rather revisit with a smaller mitigation such as retry/backoff around the existing GitHub-derived linked-issue lookup, or open a GitHub support issue with the failing run IDs. For now, closing this to avoid replacing GitHub's linked-issue source of truth with local parsing logic. |
Summary
Replaces the GraphQL
closingIssuesReferencesfield query in.github/workflows/assign-linked-issue-author.yamlwith REST + PR-body parsing for closing-keyword discovery, eliminating this workflow's dependency on a GitHub endpoint that periodically 401s onpull_request_targetruns.cc @wscurran (workflow author per
git blame).Related Issue
None — flaky third-party endpoint, no tracking issue filed yet.
Symptom that motivated this
On 2026-06-10, GitHub's GraphQL endpoint started returning HTTP 401 "Requires authentication" intermittently for this workflow's
gh pr view --json closingIssuesReferences,...calls. Within a 65-minute window (15:08–16:13 UTC), 8 runs across 6 distinct branches failed identically:codex/onboard-build-patch-config-flowe2e-migrate/test-onboard-resumemaine2e-migrate/test-model-router-provider-routed-inferencee2e-migrate/test-openclaw-tui-chat-correlatione2e-retire/test-strict-tool-call-probe(#5153)test/freeze-nightly-legacy-e2eAll hit the same signature:
Three rerun-failed attempts on PR #5153's stuck run (
27289014670) all hit the same 401 across attempts 1–3. The underlyinggithub.tokenand permissions are valid; the GraphQL endpoint itself was flaking. REST endpoints used by other workflows during the same window were unaffected (e.g.,Security / Code Scanning,nightly-e2e.yamljobs, and the REST-sidegh issue editcalls in this same workflow).This workflow is non-required (not in branch protection's required-checks set), so the cosmetic red on every affected open PR was non-blocking. But:
Why drop GraphQL entirely instead of adding retry/backoff
closingIssuesReferencescomputed field./repos/{owner}/{repo}/pulls/{N},/repos/{owner}/{repo}/issues/{N}/assignees/{user},POST /repos/{owner}/{repo}/issues/{N}/assignees) did not exhibit the flake.Changes
gh pr view --json closingIssuesReferences,...gh api repos/{owner}/{repo}/pulls/{N}+ body parsergh pr list --json closingIssuesReferences,...gh api --paginate repos/{owner}/{repo}/pulls?state=opengh issue edit --add-assigneegh api -X POST repos/{owner}/{repo}/issues/{N}/assignees -f assignees[]=loginBody parser (Python, inline in the workflow) handles GitHub's closing keywords (
close/closes/closed,fix/fixes/fixed,resolve/resolves/resolved) and same-repo reference forms:#NOWNER/REPO#N(where the repo matches${{ github.repository }})GH-Nhttps://github.com/OWNER/REPO/issues/N(same repo)Strips fenced code blocks and HTML comments before matching so example snippets don't trigger false-positive assignments. Cross-repo refs are excluded — matches prior effective behavior, since
gh issue edit --add-assigneewould have failed for cross-repo refs returned by GraphQL anyway.Trade-off acknowledged
Body-parsing does not pick up linked issues added solely via the PR sidebar's "Development" picker without a corresponding closing keyword in the body. That edge case has not been observed for this workflow's job scope; PR templates and contributor norms in this repo use explicit
Closes #N/Refs #Nlines. If we later see a regression from this, the simplest follow-up is to add a fallback that consults the timeline events REST endpoint (/repos/{owner}/{repo}/issues/{N}/timeline) forcross-referencedevents.Type of Change
Verification
python3 -c "import yaml; yaml.safe_load(...)"OKParser unit-tested in isolation against 20 cases: all 9 closing keywords (
close,closes,closed,fix,fixes,fixed,resolve,resolves,resolved),Closes:colon variant, mixed case, multiple keywords per body, dedup, same-repo qualified refs, cross-repo exclusion,GH-Nform, full-URL form, fenced-code-block exclusion, HTML-comment exclusion,fixed in #Nnon-match (matches GitHub's parser behavior — keyword and ref must be directly adjacent), empty body, null body. 20/20 pass.Manual end-to-end:
Closes #5113) → parser returns['5113']✅Refs #Nonly, no closing keywords) → parser returns[]✅npx prek run --all-filespassesnpm testpassesTests added or updated for new or changed behavior (unit-tested parser inline)
No secrets, API keys, or credentials committed
Docs updated for user-facing behavior changes
npm run docsbuilds without warnings (doc changes only)Summary by CodeRabbit