This is an automated email from the ASF dual-hosted git repository.
Yicong-Huang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new 3471e603e2 ci: auto-assign PR author, credit author on issue close
(#4551)
3471e603e2 is described below
commit 3471e603e2ee004328ce9935c5ca99b251a44c32
Author: Xinyuan Lin <[email protected]>
AuthorDate: Tue Apr 28 22:32:50 2026 -0700
ci: auto-assign PR author, credit author on issue close (#4551)
### What changes were proposed in this PR?
Adds `.github/workflows/auto-assign.yml` with two jobs:
1. **assign-pr-author** — on `opened` / `reopened` / `ready_for_review`,
assigns the PR author as the assignee. Skips bots and PRs that already
have an assignee.
2. **credit-issue-on-pr-merge** — on PR merge (`pull_request_target:
closed` + `merged == true`), uses the GraphQL `closingIssuesReferences`
edge to find the issues this PR closes, then replaces their assignees
with the PR author. This covers `Closes` / `Fixes` / `Resolves` keywords
and the manual "Linked issues" sidebar relationship.
New issues are intentionally left unassigned so the existing
`issue-triage.yml` workflow can keep them in the `triage` queue until a
human assigns them.
### Any related issues, documentation, discussions?
Closes #4550
### How was this PR tested?
Config-only change; the workflow runs on the next opened/reopened PR and
on the next PR merge. Each job guards no-op cases:
- `assign-pr-author` skips bot authors, PRs missing a user login, and
PRs that already have assignees.
- `credit-issue-on-pr-merge` skips bot authors, skips when no linked
issues exist, skips cross-repo linked issues, and only removes assignees
other than the PR author (so it's idempotent if re-run).
Permissions are scoped to `issues: write` and `pull-requests: write`.
Uses the default `GITHUB_TOKEN` — no new secrets required.
`pull_request_target` is used so PRs from forks still get assigned and
the merge-time job has write access.
### Was this PR authored or co-authored using generative AI tooling?
Generated-by: Claude Code (Opus 4.7)
---
.github/workflows/auto-assign.yml | 96 +++++++++++++++++++++++++++++++++++++++
1 file changed, 96 insertions(+)
diff --git a/.github/workflows/auto-assign.yml
b/.github/workflows/auto-assign.yml
new file mode 100644
index 0000000000..48fe10f6c2
--- /dev/null
+++ b/.github/workflows/auto-assign.yml
@@ -0,0 +1,96 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Auto-assign
+on:
+ pull_request_target:
+ types: [opened, closed]
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ assign-pr-author:
+ if: >-
+ github.event.action == 'opened'
+ && github.event.pull_request.user.type != 'Bot'
+ && github.event.pull_request.assignees[0] == null
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/github-script@v7
+ with:
+ script: |
+ const pr = context.payload.pull_request;
+ await github.rest.issues.addAssignees({
+ ...context.repo,
+ issue_number: pr.number,
+ assignees: [pr.user.login],
+ });
+
+ credit-issue-on-pr-merge:
+ if: github.event.action == 'closed' && github.event.pull_request.merged
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/github-script@v7
+ with:
+ script: |
+ const { owner, repo } = context.repo;
+ const opener = context.payload.pull_request.user;
+ const isHuman = (l) => l && !l.endsWith('[bot]');
+
+ const { repository: { pullRequest: prq } } = await github.graphql(`
+ query($owner: String!, $repo: String!, $pr: Int!) {
+ repository(owner: $owner, name: $repo) {
+ pullRequest(number: $pr) {
+ closingIssuesReferences(first: 50) {
+ nodes {
+ number
+ repository { nameWithOwner }
+ assignees(first: 20) { nodes { login } }
+ }
+ }
+ commits(first: 250) {
+ nodes { commit {
+ parents { totalCount }
+ authors(first: 10) { nodes { user { login } } }
+ } }
+ }
+ }
+ }
+ }`, { owner, repo, pr: context.payload.pull_request.number });
+
+ const authors = new Set();
+ if (opener.type !== 'Bot' && isHuman(opener.login))
authors.add(opener.login);
+ for (const { commit } of prq.commits.nodes) {
+ if (commit.parents.totalCount > 1) continue;
+ for (const a of commit.authors.nodes) {
+ if (isHuman(a.user?.login)) authors.add(a.user.login);
+ }
+ }
+ const credited = [...authors].slice(0, 10);
+ if (!credited.length) return;
+ const creditedSet = new Set(credited);
+
+ for (const issue of prq.closingIssuesReferences.nodes) {
+ if (issue.repository.nameWithOwner !== `${owner}/${repo}`)
continue;
+ const current = issue.assignees.nodes.map(n => n.login);
+ const toRemove = current.filter(l => !creditedSet.has(l));
+ const toAdd = credited.filter(l => !current.includes(l));
+ const args = { owner, repo, issue_number: issue.number };
+ if (toRemove.length) await github.rest.issues.removeAssignees({
...args, assignees: toRemove });
+ if (toAdd.length) await github.rest.issues.addAssignees({
...args, assignees: toAdd });
+ }