This is an automated email from the ASF dual-hosted git repository.

Yicong-Huang pushed a commit to branch release/v1.1.0-incubating
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/release/v1.1.0-incubating by 
this push:
     new d30cfc63fb ci: drop the `triage` label, use `is:open no:assignee` 
filter (#4899)
d30cfc63fb is described below

commit d30cfc63fb421eb41c87229c6c10c16f08be7340
Author: Yicong Huang <[email protected]>
AuthorDate: Mon May 4 07:31:47 2026 +0000

    ci: drop the `triage` label, use `is:open no:assignee` filter (#4899)
    
    ### What changes were proposed in this PR?
    
    `triage` is a derived state (issue needs triage iff it has no assignee).
    Materializing it as a label means every assignee mutation has to keep
    the label in sync, which is fragile across `GITHUB_TOKEN`-authored
    writes, third-party bots, and dropped webhooks. Drop the label;
    reviewers use the search filter `is:issue is:open no:assignee` instead.
    
    - Delete `.github/workflows/issue-triage.yml` (the entire triage-label
    state machine).
    - Add `.github/workflows/take-commands.yml`: handles `/take`
    (self-assign) and `/untake` (self-unassign) on issues.
    - Update one stale comment in `pr-assignment.yml` that referenced the
    removed workflow.
    
    `labels: ["triage"]` is left in `bug-template.yaml`,
    `task-template.yaml`, `feature-template.yaml` so that change can be
    backported on its own. Until that follow-up lands and the label itself
    is deleted from the repo, newly opened issues will continue to acquire
    the label.
    
    ### Simplified issue lifecycle
    
    The new model is "assignee = ownership; no assignee = needs triage". One
    filter and three transition rules — no reified state.
    
    ```
                      open issue, no assignee
                      (visible in `is:open no:assignee` triage filter)
                                  │
           ┌──────────────────────┼──────────────────────┐
           │                      │                      │
       /take comment         PR opened with         (no action)
       on the issue          `Closes #N`            stays in filter
           │                      │
           v                      v
      assignee = commenter    assignee = PR opener
      (out of filter)         (out of filter — tentative)
           │                      │
           │              ┌───────┴────────┐
           │              │                │
       /untake          PR closed        PR merged
       comment          unmerged
           │              │                │
           v              v                v
      removes self    opener         credit-on-merge:
      (back in        unassigned     assignees overwritten
       filter only    (back in       with PR opener +
       if no other    filter unless  commit authors.
       assignees)     others stay)   Final attribution.
    ```
    
    #### Stacked claims
    
    `/take` uses GitHub's `addAssignees`, which is append-semantics —
    existing assignees are not replaced. So if Alice has already taken an
    issue and goes inactive, Bob commenting `/take` produces a co-assignment
    rather than a rejection. Comment history records the order. `/untake`
    only removes the commenter from the assignee list, leaving anyone else
    in place. At merge time the credit step overwrites the whole list with
    the actual contributors, so stacked claims are explicitly tentative.
    
    Behavior preserved by `pr-assignment.yml` (already on main):
    - PR opened/edited with `Closes #N` → opener is auto-assigned to each
    linked issue (issue leaves the no-assignee filter).
    - PR closed without merge → opener is unassigned.
    - PR merged → the credit-on-merge step overwrites assignees with the
    actual commit authors. **Pre-merge assignments are tentative.**
    
    ### Any related issues, documentation, discussions?
    
    Closes #4898.
    
    After merge, the `triage` label can be deleted from the repo as a
    one-shot cleanup (`gh api -X DELETE
    /repos/apache/texera/labels/triage`); that drops the chip from every
    existing issue at once. Any saved searches / project-board automation
    keyed on `label:triage` should be moved to `no:assignee`.
    
    The follow-up to drop `labels: ["triage"]` from the three issue
    templates is a separate change so it can be backported independently.
    
    ### How was this PR tested?
    
    Static checks: YAML parse on both workflow files. Behavior verifiable
    post-merge: opening a PR with `Closes #N` should remove `#N` from the
    `is:open no:assignee` filter; closing the PR unmerged should put it
    back; commenting `/take` / `/untake` should toggle the assignee. Two
    users commenting `/take` in sequence should produce a stacked
    co-assignment.
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    (backported from commit 07d831158410ff33d132a7e69c091ae39f8eaf26)
    
    Generated-by: Claude Opus 4.7 (1M context)
---
 .github/workflows/issue-triage.yml  | 172 ------------------------------------
 .github/workflows/pr-assignment.yml |   6 +-
 .github/workflows/take-commands.yml |  85 ++++++++++++++++++
 3 files changed, 88 insertions(+), 175 deletions(-)

diff --git a/.github/workflows/issue-triage.yml 
b/.github/workflows/issue-triage.yml
deleted file mode 100644
index d2f13b9041..0000000000
--- a/.github/workflows/issue-triage.yml
+++ /dev/null
@@ -1,172 +0,0 @@
-# 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: Issue triage
-on:
-  issues:
-    types: [opened, assigned, unassigned]
-  issue_comment:
-    types: [created]
-
-permissions:
-  issues: write
-  pull-requests: read
-
-jobs:
-  # --------------------------------------------------------
-  # 1) Issue triage: add/remove "triage" on assignment changes
-  # --------------------------------------------------------
-  issue-triage:
-    if: github.event_name == 'issues'
-    runs-on: ubuntu-latest
-    steps:
-      - name: Add 'triage' label when issue is opened with no assignees
-        # Issue templates already attach `labels: ["triage"]`, but issues
-        # opened bare (no template, or via API/migration) miss that.
-        # Skip when the issue was opened with an assignee already in place
-        # so the assigned-handler below does not immediately remove it.
-        if: >-
-          github.event.action == 'opened'
-          && github.event.issue.assignees[0] == null
-        uses: actions/github-script@v8
-        with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          script: |
-            const { owner, repo } = context.repo;
-            const issue_number = context.payload.issue.number;
-            const labels = (context.payload.issue.labels || []).map((l) => 
l.name);
-            if (labels.includes('triage')) {
-              core.info(`Issue #${issue_number} already has 'triage' (template 
applied it).`);
-              return;
-            }
-            try {
-              await github.rest.issues.addLabels({
-                owner,
-                repo,
-                issue_number,
-                labels: ['triage'],
-              });
-              core.info(`Added 'triage' to issue #${issue_number}`);
-            } catch (e) {
-              core.warning(`Failed to add 'triage' to issue #${issue_number}: 
${e.message}`);
-            }
-
-      - name: Remove 'triage' label when issue is assigned
-        if: github.event.action == 'assigned'
-        uses: actions/github-script@v8
-        with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          script: |
-            const { owner, repo } = context.repo;
-            const issue_number = context.payload.issue.number;
-            const newAssignee = context.payload.assignee?.login || '(unknown)';
-            core.info(`Issue #${issue_number} assigned to ${newAssignee}; 
removing 'triage' label.`);
-
-            try {
-              await github.rest.issues.removeLabel({
-                owner,
-                repo,
-                issue_number,
-                name: 'triage',
-              });
-              core.info(`Removed 'triage' from issue #${issue_number}`);
-            } catch (e) {
-              if (e.status === 404) {
-                core.info(`Issue #${issue_number} has no 'triage' label, 
nothing to remove.`);
-              } else {
-                core.warning(`Failed to remove 'triage' from issue 
#${issue_number}: ${e.message}`);
-                throw e;
-              }
-            }
-
-      - name: Add 'triage' label when issue is unassigned and has no assignees
-        if: github.event.action == 'unassigned'
-        uses: actions/github-script@v8
-        with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          script: |
-            const issue = context.payload.issue;
-            const { owner, repo } = context.repo;
-            const issue_number = issue.number;
-            const removed = context.payload.assignee?.login || '(unknown)';
-
-            const assignees = issue.assignees || [];
-            core.info(`Issue #${issue_number} unassigned ${removed}; remaining 
assignees: [${assignees.map((a) => a.login).join(', ') || '(none)'}]`);
-            if (assignees.length > 0) {
-              core.info(
-                `Issue #${issue_number} still has ${assignees.length} 
assignee(s), not adding 'triage'.`
-              );
-              return;
-            }
-
-            core.info(`Issue #${issue_number} has no assignees, adding 
'triage' label.`);
-            try {
-              await github.rest.issues.addLabels({
-                owner,
-                repo,
-                issue_number,
-                labels: ['triage'],
-              });
-              core.info(`Added 'triage' to issue #${issue_number}`);
-            } catch (e) {
-              core.warning(`Failed to add 'triage' to issue #${issue_number}: 
${e.message}`);
-              throw e;
-            }
-
-  # --------------------------------------------------------
-  # 2) /take comment self-claim: anyone commenting "/take" on
-  #    an issue (not on a PR) gets assigned to it. The
-  #    resulting issues.assigned event then drops the 'triage'
-  #    label via the job above. The startsWith filter at the
-  #    job level keeps non-/take comments from allocating a
-  #    runner; the regex inside the script enforces an exact
-  #    "/take" line so suffixes like "/take this" do not
-  #    silently match.
-  # --------------------------------------------------------
-  take-comment:
-    if: >-
-      github.event_name == 'issue_comment'
-      && github.event.action == 'created'
-      && github.event.issue.pull_request == null
-      && github.event.comment.user.type != 'Bot'
-      && startsWith(github.event.comment.body, '/take')
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/github-script@v8
-        with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
-          script: |
-            const body = (context.payload.comment.body || '').trim();
-            const issue_number = context.payload.issue.number;
-            const login = context.payload.comment.user.login;
-            core.info(`/take candidate: comment by ${login} on issue 
#${issue_number}; body=${JSON.stringify(body)}`);
-            if (!/^\/take\s*$/.test(body)) {
-              core.info(`Comment does not match exact '/take'; skipping.`);
-              return;
-            }
-
-            const { owner, repo } = context.repo;
-            try {
-              await github.rest.issues.addAssignees({
-                owner,
-                repo,
-                issue_number,
-                assignees: [login],
-              });
-              core.info(`Assigned ${login} to issue #${issue_number}`);
-            } catch (e) {
-              core.warning(`Failed to assign ${login} to #${issue_number}: 
${e.message}`);
-            }
diff --git a/.github/workflows/pr-assignment.yml 
b/.github/workflows/pr-assignment.yml
index 92a6515f4e..193e02b825 100644
--- a/.github/workflows/pr-assignment.yml
+++ b/.github/workflows/pr-assignment.yml
@@ -143,9 +143,9 @@ jobs:
         # When a PR is closed without merging, the opener was added to
         # linked issues by the "Sync PR opener" step on open/edit. Mirror
         # the close: remove them so abandoned PRs do not leave stale
-        # assignees. The issue-triage workflow's unassigned handler then
-        # re-adds `triage` if no other assignees remain. Cross-repo refs
-        # are skipped, consistent with the assign side.
+        # assignees. With no other assignee the issue then drops back into
+        # the `is:open no:assignee` triage filter automatically. Cross-repo
+        # refs are skipped, consistent with the assign side.
         if: >-
           github.event.action == 'closed'
           && github.event.pull_request.merged == false
diff --git a/.github/workflows/take-commands.yml 
b/.github/workflows/take-commands.yml
new file mode 100644
index 0000000000..bf567f6240
--- /dev/null
+++ b/.github/workflows/take-commands.yml
@@ -0,0 +1,85 @@
+# 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.
+
+# /take and /untake comment commands.
+#
+# Triage state is no longer materialized as a label — it is the search
+# filter `is:issue is:open no:assignee`. Anyone can self-claim an issue
+# by commenting `/take` (and self-release with `/untake`); PR-driven
+# assignee sync is handled by `pr-assignment.yml`.
+name: Issue take commands
+on:
+  issue_comment:
+    types: [created]
+
+permissions:
+  issues: write
+
+jobs:
+  take:
+    # The startsWith filter at the job level keeps unrelated comments
+    # from allocating a runner; the regex inside the script enforces an
+    # exact `/take` or `/untake` so suffixes like `/take this` do not
+    # silently match.
+    if: >-
+      github.event_name == 'issue_comment'
+      && github.event.action == 'created'
+      && github.event.issue.pull_request == null
+      && github.event.comment.user.type != 'Bot'
+      && (startsWith(github.event.comment.body, '/take')
+          || startsWith(github.event.comment.body, '/untake'))
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/github-script@v8
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const body = (context.payload.comment.body || '').trim();
+            const issue_number = context.payload.issue.number;
+            const login = context.payload.comment.user.login;
+            const { owner, repo } = context.repo;
+            core.info(
+              `take/untake candidate: ${login} on issue #${issue_number}; ` +
+                `body=${JSON.stringify(body)}`,
+            );
+
+            if (/^\/take\s*$/.test(body)) {
+              try {
+                await github.rest.issues.addAssignees({
+                  owner, repo, issue_number, assignees: [login],
+                });
+                core.info(`Assigned ${login} to issue #${issue_number}`);
+              } catch (e) {
+                core.warning(
+                  `addAssignees on #${issue_number} failed: ${e.message}`,
+                );
+              }
+            } else if (/^\/untake\s*$/.test(body)) {
+              try {
+                await github.rest.issues.removeAssignees({
+                  owner, repo, issue_number, assignees: [login],
+                });
+                core.info(`Unassigned ${login} from issue #${issue_number}`);
+              } catch (e) {
+                core.warning(
+                  `removeAssignees on #${issue_number} failed: ${e.message}`,
+                );
+              }
+            } else {
+              core.info(
+                `Comment does not match exact '/take' or '/untake'; skipping.`,
+              );
+            }

Reply via email to