Skip to content

feat(ci): add issue and PR management automation#262

Open
lockwobr wants to merge 1 commit into
mainfrom
chore/issue-management-automation
Open

feat(ci): add issue and PR management automation#262
lockwobr wants to merge 1 commit into
mainfrom
chore/issue-management-automation

Conversation

@lockwobr
Copy link
Copy Markdown
Collaborator

What

Imports the issue/PR housekeeping automation adapted from NVIDIA/aicr, and migrates the issue taxonomy onto native GitHub Issue Types. Closes #260.

New workflows (.github/workflows/)

  • triage — labels new issues needs-triage, infers component/* labels from the Component dropdown → conventional-commit scope → title/body keywords (applies all selected components), and clears needs-triage once triaged.
  • labeler — path-based component/* / doc labels on PRs (actions/labeler).
  • welcome — greets first-time issue and PR authors.
  • lock-threads — locks issues/PRs closed for 90+ days.
  • inactive-pr-reminder — nudges PRs idle 14–29 days, before the stale bot fires at 30.

Config / labels

  • New .github/labeler.yml path→label map. component/ci covers .github/, scripts/, .gitlab-ci.yml, and Makefiles (vendored Makefiles excluded).
  • .github/labels.yml: added needs-triage, component/ci, component/tests; gave the component/* family distinct hues; retired bug/feature/epic/task/initiative (now native Issue Types); kept doc as a PR path label.

Issue templates

  • Bug / Feature forms now set the Bug / Enhancement Issue Type instead of a label, and gain a multi-select Component dropdown.
  • Repo links updated NVIDIA/skyhookNVIDIA/nodewright.

Other

  • Dropped the dead P0 exemption from stale.yaml (priority labels are gone).

Already applied to the live repo

  • Retyped the few issues carrying the old bug/feature labels and deleted bug/feature/epic/task/initiative (covered by org Issue Types).
  • Ran make labels to sync the new label set/colors.

Notes

  • Only external actions are actions/github-script and actions/labeler, pinned to the same SHAs as aicr and explicitly on the org allowlist (github_owned_allowed + explicit patterns).
  • Validated with actionlint (clean) and YAML parse on all touched files.
  • No docs/ page covers the label taxonomy / triage flow, so nothing there was stale to update.

@lockwobr lockwobr self-assigned this May 29, 2026
Import issue/PR housekeeping workflows adapted from NVIDIA/aicr:

- triage: label new issues needs-triage, infer component/* labels from the
  Component dropdown, conventional-commit scope, and title/body text
- labeler: path-based component/* and doc labels on PRs
- welcome: greet first-time issue and PR authors
- lock-threads: lock issues/PRs closed for 90+ days
- inactive-pr-reminder: nudge PRs idle 14-29 days before the stale bot

Migrate issue taxonomy to native GitHub Issue Types: the bug and feature
forms now set the Bug/Enhancement type instead of a label, gain a
multi-select Component dropdown, and the bug/feature/epic/task/initiative
labels are retired. Add needs-triage plus component/ci and component/tests
with distinct hues, and drop the dead P0 exemption from the stale workflow.

All external actions (actions/github-script, actions/labeler) match the
SHAs already allowlisted for the org.

Signed-off-by: Brian Lockwood <lockwobr@gmail.com>
@lockwobr lockwobr force-pushed the chore/issue-management-automation branch from a92da00 to 8c64bd8 Compare May 29, 2026 23:32
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds GitHub repo automation and label routing: issue forms (bug/feature) now use native org types (Bug, Enhancement), default to needs-triage, and include a required multi-select Component field. It introduces a path-based labeler config and workflows to auto-label PRs, triage issues (infer/add component labels and remove needs-triage), welcome first-time contributors, lock closed threads older than 90 days, and remind authors of PRs inactive 14–29 days. The stale workflow no longer exempts P0 issues.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • mskalka
  • rice-riley
  • ayuskauskas
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(ci): add issue and PR management automation' clearly and concisely summarizes the main change: adding automation workflows for issue and PR management, which is the primary focus of the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing the imported automation workflows, new configuration files, issue template updates, and migration to native GitHub Issue Types, all of which are present in the code changes.
Linked Issues check ✅ Passed All primary coding objectives from issue #260 are fully met: triage workflow with needs-triage and component inference [#260], path-based labeler with component labels and labeler.yml config [#260], welcome workflow for first-time contributors [#260], lock-threads for stale threads [#260], inactive-pr-reminder for idle PRs [#260], updated issue templates with Component dropdown [#260], and label configuration updates [#260].
Out of Scope Changes check ✅ Passed All changes directly support the linked issue #260 objectives: new workflows (triage, labeler, welcome, lock-threads, inactive-pr-reminder), labeler.yml configuration, labels.yml updates, issue template modifications (Bug/Feature forms with Component dropdown and Issue Type), repo link updates (skyhook→nodewright), and stale.yaml P0 exemption removal are all explicitly in scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/issue-management-automation

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 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/labeler.yml:
- Around line 21-28: The labeler rules use negated patterns under
all-globs-to-all-files which makes a rule fail if any changed file matches the
negation (e.g., component/operator uses all-globs-to-all-files:
'!operator/cmd/cli/**' and component/ci uses '!**/vendor/**'); update
.github/labeler.yml by removing those all-globs-to-all-files negation blocks and
instead add the negated patterns into the corresponding any-glob-to-any-file
lists (e.g., add '!operator/cmd/cli/**' to component/operator's
any-glob-to-any-file and '!**/vendor/**' to component/ci's
any-glob-to-any-file), ensuring the keys any-glob-to-any-file remain the
positive include patterns plus the negated excludes so mixed-file PRs still
match the intended label rules.

In @.github/workflows/inactive-pr-reminder.yaml:
- Around line 48-55: The PR listing currently only fetches one page via
github.rest.pulls.list (per_page: 100) and will miss PRs beyond the first 100;
change this to fetch all pages (e.g., use github.paginate or octokit.paginate
with the github.rest.pulls.list endpoint) and aggregate the results into a
single prs array before filtering. Locate the call to github.rest.pulls.list and
replace it with a paginated call that passes the same params (owner, repo,
state, sort, direction, per_page) and flattens all pages into prs so downstream
logic that processes prs sees every open PR.

In @.github/workflows/lock-threads.yaml:
- Around line 50-57: The list calls for closed issues and pulls use a single
page (per_page: 100) and skip additional pages; replace those single calls with
paginated retrieval (use github.paginate or an explicit page loop) to collect
all results. Specifically, change the github.rest.issues.listForRepo(...) and
github.rest.pulls.list(...) calls to use
github.paginate(github.rest.issues.listForRepo, {...}) and
github.paginate(github.rest.pulls.list, {...}) (or a while/page increment loop)
with the same params (owner, repo, state, sort, direction, per_page) so you
aggregate all pages into a single array before filtering/locking.

In @.github/workflows/triage.yaml:
- Around line 214-233: The current workflow clears 'needs-triage' whenever any
non-'needs-triage' label is added; change the logic to only clear 'needs-triage'
when a triage-signaling label is explicitly added. Replace the broad condition
(github.event.label.name != 'needs-triage') with a check against an
allow-list/array of triage labels (e.g., ALLOWED_TRIAGE_LABELS) and only proceed
when github.event.label.name is one of those; inside the actions/github-script
block also verify the added label is in that allow-list before calling
github.rest.issues.removeLabel (keeping the existing labels list check for
'needs-triage') so automated labels like 'lifecycle/stale' won't clear triage
status.

In @.github/workflows/welcome.yaml:
- Around line 45-47: The two separate searches using
github.rest.search.issuesAndPullRequests (one with `type:issue` and another with
`type:pr`) allow a user to be welcomed twice; change the queries to count
contributions across both issues and PRs by removing the `type:` filter (or
otherwise querying both kinds in one request) so the `total_count` for `creator`
reflects all contributions in the repo; update the code paths that currently run
the issue-only and pr-only searches to use this unified search (references:
github.rest.search.issuesAndPullRequests, the `total_count` destructuring, and
the `creator` variable).
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: adb0f7aa-ffff-44d0-9fec-e406484ca172

📥 Commits

Reviewing files that changed from the base of the PR and between b264019 and a92da00.

📒 Files selected for processing (11)
  • .github/ISSUE_TEMPLATE/bug_report_form.yml
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/ISSUE_TEMPLATE/feature_request_form.yml
  • .github/labeler.yml
  • .github/labels.yml
  • .github/workflows/inactive-pr-reminder.yaml
  • .github/workflows/labeler.yaml
  • .github/workflows/lock-threads.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/triage.yaml
  • .github/workflows/welcome.yaml

Comment thread .github/labeler.yml
Comment on lines +21 to +28
# Operator (controller-manager), excluding the CLI which lives under it.
component/operator:
- changed-files:
- any-glob-to-any-file:
- 'operator/**'
- all-globs-to-all-files:
- '!operator/cmd/cli/**'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In actions/labeler v6, how do all-globs-to-all-filesand negative globs behave for mixed-file PRs, and what pattern should be used to exclude a subpath (for exampleoperator/cmd/cli/or/vendor/**) without suppressing the label for the whole PR?

💡 Result:

In actions/labeler, the all-globs-to-all-files setting requires that every glob pattern provided in its list must match against every single file changed in the pull request [1][2]. If any file in the PR fails to match any of the specified globs, the condition will not be met [1][2]. When using negative globs (prefixed with !) within this context, you are effectively adding an exclusion constraint to the global requirement [1][3]. Because all-globs-to-all-files is an "AND" operation across all provided patterns, adding a negative glob means that to satisfy the condition, all files must match the positive patterns AND none of the files may match the negative patterns [1][2]. To exclude a subpath (like operator/cmd/cli/ or /vendor/) without suppressing the label for the entire PR, you cannot simply use a negative glob inside an all-globs-to-all-files block, as this would likely disqualify the label if any file exists in those subpaths. Instead, you should use the any and all top-level keys to structure your logic [1][2]. Recommended Pattern: To apply a label based on certain file changes while explicitly ignoring specific subpaths, structure your configuration to combine any-glob-to-any-file (which triggers the label) with an all-globs-to-all-files exclusion or by using multiple rules [1][2]. For example, to label changes in src while excluding src/docs, you can use this structure [1][2]: label-name: - all: - changed-files: - any-glob-to-any-file: 'src//' - all-globs-to-all-files: '!src/docs/' This configuration uses any-glob-to-any-file to ensure the label is applied if any file in src is changed, while the all-globs-to-all-files with a negative glob acts as a filter that must also be satisfied (i.e., all changed files must not be in src/docs) [1][2]. If you want to allow changes to src/docs without triggering the label, you would typically define separate rules where the exclusion is handled by the scope of the matching glob itself rather than relying on global negation [4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant labeler rules around the reported line ranges
if [ -f .github/labeler.yml ]; then
  echo "=== .github/labeler.yml (lines 1-120) ==="
  nl -ba .github/labeler.yml | sed -n '1,120p'
else
  echo "Missing: .github/labeler.yml"
  exit 1
fi

Repository: NVIDIA/nodewright

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

if [ -f .github/labeler.yml ]; then
  echo "=== .github/labeler.yml (lines 1-120) ==="
  cat -n .github/labeler.yml | sed -n '1,120p'
else
  echo "Missing: .github/labeler.yml"
  exit 1
fi

Repository: NVIDIA/nodewright

Length of output: 2446


🌐 Web query:

actions/labeler changed-files all-globs-to-all-files negative glob ! semantics

💡 Result:

For actions/labeler, the semantics of the “negative glob” syntax with the! prefix inside the changed-files globs are: the glob pattern is treated as a negation (i.e., it should exclude paths), but it only makes sense in the context of the action’s described matcher logic (any/all combinations) and depends on matching all changed files vs any changed files. 1) What all-globs-to-all-files means actions/labeler defines these operators for changed-files matching across the list of changed paths: - any-glob-to-all-files: ANY glob must match ALL changed files. - all-globs-to-all-files: ALL globs must match ALL changed files. This is explicitly documented for actions/labeler’s changed-files options. [1] So with all-globs-to-all-files, every glob entry in that list must match every changed file for the rule to apply. [1] 2) How negation is written (the leading!) In actions/labeler, negation is used by prefixing a glob pattern with! (example shown in the README): - all-globs-to-all-files: '!src/docs/' The example is presented as part of a configuration where the rule matches src// but excludes the docs subfolder via a negated glob. [1] 3) Practical consequence: with all-globs-to-all-files, negation can become “too strict” Because all-globs-to-all-files requires that every glob (including a negated one) match every changed file, negated patterns work only if the negated pattern “matches” (i.e., is satisfied) for each changed path according to the action’s matcher. The README’s wording and example imply this is intended as “match all src files except docs” by combining a positive glob with an excluded docs glob within an all/changed-files context. [1] If you put only a negated glob in all-globs-to-all-files, you are effectively requiring that every changed file satisfies the negation constraint; if any changed file doesn’t satisfy it, the rule won’t apply. 4) Example pattern from the official docs The README shows: - changed-files: - any-glob-to-any-file: 'src//' - all-globs-to-all-files: '!src/docs/' This demonstrates the intended use of! to exclude docs while still matching source files under src. [1] Bottom line - Use! to express exclusion within actions/labeler’s changed-files glob list. [1] - all-globs-to-all-files means every glob entry must satisfy the rule for every changed file. [1] - Therefore, negated globs under all-globs-to-all-files exclude paths only insofar as the rule can still be satisfied for all changed files. [1]

Citations:


🌐 Web query:

actions/labeler changed-files configuration is list of matchers combined with AND or OR

💡 Result:

In the official actions/labeler GitHub Action, configuration logic is handled using two primary top-level keys: any and all [1][2]. From a boolean logic perspective, these function as follows: - Options within the all key are combined with an AND operation, meaning all provided conditions must be met for the label to be applied [1][3]. - Individual match rules within the any object are combined with an OR operation, meaning if any of the provided conditions match, the label will be applied [1][4]. - Additionally, top-level match objects are combined using an AND operation [2][3]. If a configuration is provided without an explicit top-level key (any or all), it defaults to any [1][5]. The action supports fine-grained matching, including base-branch, head-branch, and various changed-files glob matching strategies (such as any-glob-to-any-file or all-globs-to-all-files) [1][3].

Citations:


🌐 Web query:

actions/labeler any-glob-to-any-file negated glob ! operator/cmd/cli/** behavior

💡 Result:

In the GitHub actions/labeler, the exclamation mark (!) operator is used for glob negation to create complex matching rules by excluding specific files or paths [1][2]. When combined with glob patterns, this allows you to specify which files to include while explicitly ignoring others [1][3]. Key behaviors and considerations include: - Functionality: The! operator functions as a NOT filter within your path glob definitions [1][2]. If a file matches a negated pattern, it will be excluded from the match, even if it otherwise satisfies other inclusion patterns in your configuration [1][3]. - Configuration: Glob patterns in actions/labeler are evaluated based on the Minimatch library [1]. When using negation, it is often necessary to use explicit inclusion patterns alongside your exclusions to achieve the desired logic [1][2]. - YAML Syntax: Because the asterisk () and exclamation mark (!) have special meanings in YAML (used for aliases and tags/anchors, respectively), you must wrap your glob strings in quotes (e.g., '!src//') to ensure they are interpreted as literal strings rather than YAML syntax [4]. Failing to quote these patterns can cause the action to crash or fail to parse the configuration correctly [4]. Example Usage: To label a pull request based on changes to a directory while excluding a specific subdirectory, you might structure your configuration like this: - all: - changed-files: - any-glob-to-any-file: 'src//' - any-glob-to-any-file: '!src/exclude-folder//' This ensures that any file in src/ is considered, but files within src/exclude-folder/ are ignored [1][2].

Citations:


🌐 Web query:

actions/labeler all-globs-to-all-files '!src/docs/*' PR changes both src and src/docs what happens

💡 Result:

When using the actions/labeler configuration all-globs-to-all-files with a negated glob like '!src/docs/', the behavior is governed by strict boolean logic applied to the set of changed files in the Pull Request [1][2][3]. The rule all-globs-to-all-files requires that ALL specified globs must match against ALL changed files [1][3]. If a PR changes files in both src/ (e.g., src/main.js) and src/docs/ (e.g., src/docs/readme.md): 1. The set of changed files contains files that match the negation '!src/docs/' (the src/main.js file) and files that do not (the src/docs/readme.md file). 2. Because the negation '!src/docs/*' is treated as a requirement that matches files NOT in the docs directory, the rule essentially checks if all changed files are outside the docs directory. 3. Since the PR contains at least one file inside src/docs/, the condition 'all files must match the glob' fails. Consequently, the label will not be applied [1][3]. In summary, when you use a negation with all-globs-to-all-files, the rule only evaluates to true if every single file changed in the PR satisfies the condition of not being in the docs/ directory [1][2]. If even one file is changed within src/docs/, the entire rule fails because not all changed files satisfy the negated pattern [1][3].

Citations:


🌐 Web query:

actions/labeler mixed PR exclude subpath do not suppress label operator/cmd/cli all-globs-to-all-files '!**' pattern recommended

💡 Result:

The “actions/labeler” action supports excluding subpaths via negated path globs using the “!” prefix inside the glob list. In the documented example, to exclude the docs sub-folder while labeling src files, the recommended pattern is: - all-globs-to-all-files: '!src/docs/' [1] In practice (and per a resolved issue report), exclusions may require array syntax and quotes; the reporter’s working fix changed from a bare string negation to a single-element list in YAML: - all-globs-to-all-files: '[!docs/source/news/index.rst]' [2] Regarding “mixed PR exclude subpath do not suppress label operator/cmd/cli all-globs-to-all-files '!' pattern”: the sources above do not document an exclusion pattern specifically using '!' (nor any special “operator/cmd/cli” semantics). What is clearly supported is negated globs combined with the all-globs-to-all-files matching mode, and the canonical negation format is '!' (with recommended YAML quoting/list form when needed) [1][2]. So, for excluding a specific file/subpath under an otherwise-matched directory, use a negated glob like '!operator/cmd/cli//' (or '!operator/cmd/cli/') as shown in the official docs pattern for “EXCEPT” cases, and if it doesn’t behave as expected, ensure you use the YAML quoting/list form for the glob string, as in the resolved issue [1][2].

Citations:


🌐 Web query:

actions/labeler any-glob-to-any-file negated glob ! operator/cmd/cli behavior mixed files

💡 Result:

In the GitHub actions/labeler action, the negation operator (!) is supported for path glob patterns [1][2]. When using these patterns with configuration options like any-glob-to-any-file, the negation operator effectively excludes files or directories that would otherwise be matched by a glob [1][2]. For example, a configuration can use negation to select a broad set of files while excluding specific subsets: - changed-files: - any-glob-to-any-file: ['src//', '!src/docs/'] In this example, the rule matches any change within the src directory, excluding any changes specifically within the src/docs directory [2]. Behavioral considerations: 1. Complex Matching: Negation allows you to create more granular label logic by combining inclusion and exclusion in a single list [1]. 2. Order of Operations: The labeler processes these globs using standard glob matching libraries (minimatch), where the negation pattern (!) instructs the matcher to treat the pattern as an exclusion rule rather than an inclusion rule [3][1]. 3. Logic Context: Since any-glob-to-any-file requires that at least one of the provided globs matches a changed file, negation patterns effectively reduce the set of files that qualify for a match [1][4]. 4. Limitations: While powerful, be aware that complex negated glob sets can become difficult to debug. If a PR contains only files that are negated, the condition will not be met [1]. For best results, keep patterns simple and avoid deeply nested or contradictory negation chains. You can combine multiple conditions using the top-level all or any keys to further refine the logic [2][4].

Citations:


Fix actions/labeler glob exclusions so mixed-file PRs don’t lose labels

  • .github/labeler.yml component/operator uses all-globs-to-all-files: '!operator/cmd/cli/**', which makes the rule fail if any changed file is under operator/cmd/cli/**—so PRs touching both CLI and non-CLI operator code lose component/operator.
  • Same pattern for component/ci: all-globs-to-all-files: '!**/vendor/**' suppresses component/ci whenever the PR includes any vendored file (even alongside real CI changes).

Move the ! exclusions into the any-glob-to-any-file matcher (and remove the negated all-globs-to-all-files blocks):

component/operator:
  - changed-files:
      - any-glob-to-any-file:
          - 'operator/**'
          - '!operator/cmd/cli/**'

component/ci:
  - changed-files:
      - any-glob-to-any-file:
          - '.github/**'
          - 'scripts/**'
          - '.gitlab-ci.yml'
          - '**/Makefile'
          - '**/*.mk'
          - '!**/vendor/**'
🤖 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/labeler.yml around lines 21 - 28, The labeler rules use negated
patterns under all-globs-to-all-files which makes a rule fail if any changed
file matches the negation (e.g., component/operator uses all-globs-to-all-files:
'!operator/cmd/cli/**' and component/ci uses '!**/vendor/**'); update
.github/labeler.yml by removing those all-globs-to-all-files negation blocks and
instead add the negated patterns into the corresponding any-glob-to-any-file
lists (e.g., add '!operator/cmd/cli/**' to component/operator's
any-glob-to-any-file and '!**/vendor/**' to component/ci's
any-glob-to-any-file), ensuring the keys any-glob-to-any-file remain the
positive include patterns plus the negated excludes so mixed-file PRs still
match the intended label rules.

Comment on lines +48 to +55
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
sort: 'updated',
direction: 'asc',
per_page: 100,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/inactive-pr-reminder.yaml"
echo "== File =="
ls -la "$FILE"

echo "== Lines 1-120 =="
nl -ba "$FILE" | sed -n '1,120p'

echo "== Search for paginate usage =="
rg -n "paginate|pagination|per_page|page:" "$FILE" || true

echo "== Search for pulls.list usage =="
rg -n "pulls\.list" "$FILE"

Repository: NVIDIA/nodewright

Length of output: 244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/inactive-pr-reminder.yaml"
echo "== File =="
ls -la "$FILE"

echo "== Lines 1-160 =="
awk 'NR>=1 && NR<=160 {printf "%4d  %s\n", NR, $0}' "$FILE"

echo "== Search for paginate usage =="
rg -n "paginate|pagination|per_page|page:" "$FILE" || true

echo "== Search for pulls.list usage =="
rg -n "pulls\.list" "$FILE" || true

echo "== Show surrounding context for pulls.list =="
rg -n -C 3 "pulls\.list" "$FILE" || true

Repository: NVIDIA/nodewright

Length of output: 4210


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/inactive-pr-reminder.yaml"

echo "== Lines 70-140 =="
awk 'NR>=70 && NR<=160 {printf "%4d  %s\n", NR, $0}' "$FILE"

Repository: NVIDIA/nodewright

Length of output: 861


🌐 Web query:

actions/github-script github.paginate.iterator pulls.list

💡 Result:

To use github.paginate.iterator within actions/github-script, you pass an Octokit endpoint method or a string representing the endpoint, along with its required parameters [1][2][3]. The iterator provides an asynchronous way to process paginated results one page at a time rather than fetching all pages into memory at once [1][2]. For pulls.list, you should pass the endpoint method directly (e.g., github.rest.pulls.list) rather than calling it first, as the iterator requires the endpoint reference to manage pagination automatically [1][2][3]. Here is an example of how to implement this in actions/github-script: javascript // Example: Iterating through pull requests using github.paginate.iterator const iterator = github.paginate.iterator(github.rest.pulls.list, { owner: context.repo.owner, repo: context.repo.repo, per_page: 100, // Optional: adjust page size as needed state: 'open' }); for await (const { data: pulls } of iterator) { for (const pull of pulls) { console.log(`Processing PR #${pull.number}: ${pull.title}`); } } Key points for implementation: - Direct Method Pass: Ensure you pass the reference to the function (e.g., github.rest.pulls.list), not the result of executing that function [1][2][3]. - Async Iterator: Because it returns an async iterator, you must use for await...of to process the responses [1][2]. - Memory Efficiency: Using the iterator is more memory-efficient than github.paginate(), which aggregates all pages into a single array before returning [4][2]. - Error Handling: Always wrap your iteration in a try-catch block to handle potential network errors or API rate limiting gracefully [2][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE=".github/workflows/inactive-pr-reminder.yaml"
awk '{printf "%4d  %s\n", NR, $0}' "$FILE"

Repository: NVIDIA/nodewright

Length of output: 3572


Paginate open PR listing to avoid missing eligible PRs beyond the first 100 results.

File: .github/workflows/inactive-pr-reminder.yaml
Lines: 48-55

            const { data: prs } = await github.rest.pulls.list({
              owner: context.repo.owner,
              repo: context.repo.repo,
              state: 'open',
              sort: 'updated',
              direction: 'asc',
              per_page: 100,
            });

Comment: The workflow fetches a single page (per_page: 100) and iterates only that first result set. If the repo has more than 100 open PRs, eligible 14–29 day candidates on later pages won’t be reminded.

Suggested fix
-            const { data: prs } = await github.rest.pulls.list({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              state: 'open',
-              sort: 'updated',
-              direction: 'asc',
-              per_page: 100,
-            });
-
-            for (const pr of prs) {
+            for await (const { data: prs } of github.paginate.iterator(
+              github.rest.pulls.list,
+              {
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                state: 'open',
+                sort: 'updated',
+                direction: 'asc',
+                per_page: 100,
+              }
+            )) {
+              for (const pr of prs) {
               // Skip PRs explicitly held open or parked.
               if (pr.labels.some(l => skipLabels.includes(l.name))) continue;
@@
               await github.rest.issues.createComment({
                 owner: context.repo.owner,
                 repo: context.repo.repo,
                 issue_number: pr.number,
                 body: `@${pr.user.login} this PR has been inactive for ${daysInactive} days. Do you need help finishing it, or should we close it for now? Feel free to reopen anytime.`,
               });
+              }
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
sort: 'updated',
direction: 'asc',
per_page: 100,
});
for await (const { data: prs } of github.paginate.iterator(
github.rest.pulls.list,
{
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
sort: 'updated',
direction: 'asc',
per_page: 100,
}
)) {
for (const pr of prs) {
// Skip PRs explicitly held open or parked.
if (pr.labels.some(l => skipLabels.includes(l.name))) continue;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `@${pr.user.login} this PR has been inactive for ${daysInactive} days. Do you need help finishing it, or should we close it for now? Feel free to reopen anytime.`,
});
}
}
🤖 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/inactive-pr-reminder.yaml around lines 48 - 55, The PR
listing currently only fetches one page via github.rest.pulls.list (per_page:
100) and will miss PRs beyond the first 100; change this to fetch all pages
(e.g., use github.paginate or octokit.paginate with the github.rest.pulls.list
endpoint) and aggregate the results into a single prs array before filtering.
Locate the call to github.rest.pulls.list and replace it with a paginated call
that passes the same params (owner, repo, state, sort, direction, per_page) and
flattens all pages into prs so downstream logic that processes prs sees every
open PR.

Comment on lines +50 to +57
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'closed',
sort: 'updated',
direction: 'asc',
per_page: 100,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/lock-threads.yaml"
echo "== File: $FILE =="
wc -l "$FILE"
echo

echo "== Lines 1-140 =="
nl -ba "$FILE" | sed -n '1,140p'
echo

echo "== Search for pagination usage =="
rg -n "paginate|pagination|github\\.paginate|per_page\\s*:\\s*[0-9]+" "$FILE" || true
echo

echo "== Extract calls around issues listForRepo and pulls.list =="
rg -n "issues\\.listForRepo|pulls\\.list" "$FILE"
echo

Repository: NVIDIA/nodewright

Length of output: 230


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/lock-threads.yaml"
echo "== File: $FILE =="
wc -l "$FILE"
echo

echo "== Lines 1-140 =="
sed -n '1,140p' "$FILE" | cat -n
echo

echo "== Search for pagination usage =="
rg -n "paginate|pagination|github\.paginate" "$FILE" || true
rg -n "per_page\s*:\s*[0-9]+" "$FILE" || true
echo

echo "== Calls around listForRepo and pulls.list =="
rg -n "issues\.listForRepo|pulls\.list" "$FILE"
echo

echo "== Context around those calls (±20 lines) =="
# Extract line numbers first, then show surrounding context.
rg -n "issues\.listForRepo|pulls\.list" "$FILE" | cut -d: -f1 | sort -u | while read -r ln; do
  start=$((ln-20)); end=$((ln+20))
  echo "---- around line $ln (showing $start-$end) ----"
  sed -n "${start},${end}p" "$FILE" | cat -n
  echo
done

Repository: NVIDIA/nodewright

Length of output: 8938


Paginate closed issue/PR REST list calls to avoid skipping eligible stale threads (lines ~50-57 and ~79-86).
Both github.rest.issues.listForRepo and github.rest.pulls.list are called once with per_page: 100, with no pagination loop—so any additional qualifying closed items beyond the first page will never be evaluated/locked.

Suggested fix
-            const { data: issues } = await github.rest.issues.listForRepo({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              state: 'closed',
-              sort: 'updated',
-              direction: 'asc',
-              per_page: 100,
-            });
-
-            for (const issue of issues) {
-              if (issue.locked || issue.pull_request) continue;
-              const updated = new Date(issue.updated_at);
-              if ((now - updated) / msPerDay < inactiveDays) continue;
+            for await (const { data: issues } of github.paginate.iterator(
+              github.rest.issues.listForRepo,
+              {
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                state: 'closed',
+                sort: 'updated',
+                direction: 'asc',
+                per_page: 100,
+              }
+            )) {
+              for (const issue of issues) {
+                if (issue.locked || issue.pull_request) continue;
+                const updated = new Date(issue.updated_at);
+                if ((now - updated) / msPerDay < inactiveDays) continue;
 
-              await github.rest.issues.createComment({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: issue.number,
-                body: 'This issue has been automatically locked since it has been closed for 90 days with no further activity. Please open a new issue for related concerns.',
-              });
-              await github.rest.issues.lock({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: issue.number,
-                lock_reason: 'resolved',
-              });
+                await github.rest.issues.createComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: issue.number,
+                  body: 'This issue has been automatically locked since it has been closed for 90 days with no further activity. Please open a new issue for related concerns.',
+                });
+                await github.rest.issues.lock({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: issue.number,
+                  lock_reason: 'resolved',
+                });
+              }
             }
 
-            const { data: prs } = await github.rest.pulls.list({
-              owner: context.repo.owner,
-              repo: context.repo.repo,
-              state: 'closed',
-              sort: 'updated',
-              direction: 'asc',
-              per_page: 100,
-            });
-
-            for (const pr of prs) {
-              if (pr.locked) continue;
-              const updated = new Date(pr.updated_at);
-              if ((now - updated) / msPerDay < inactiveDays) continue;
+            for await (const { data: prs } of github.paginate.iterator(
+              github.rest.pulls.list,
+              {
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                state: 'closed',
+                sort: 'updated',
+                direction: 'asc',
+                per_page: 100,
+              }
+            )) {
+              for (const pr of prs) {
+                if (pr.locked) continue;
+                const updated = new Date(pr.updated_at);
+                if ((now - updated) / msPerDay < inactiveDays) continue;
 
-              await github.rest.issues.createComment({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: pr.number,
-                body: 'This pull request has been automatically locked since it has been closed for 90 days with no further activity. Please open a new pull request for related changes.',
-              });
-              await github.rest.issues.lock({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: pr.number,
-                lock_reason: 'resolved',
-              });
+                await github.rest.issues.createComment({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: pr.number,
+                  body: 'This pull request has been automatically locked since it has been closed for 90 days with no further activity. Please open a new pull request for related changes.',
+                });
+                await github.rest.issues.lock({
+                  owner: context.repo.owner,
+                  repo: context.repo.repo,
+                  issue_number: pr.number,
+                  lock_reason: 'resolved',
+                });
+              }
             }
🤖 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/lock-threads.yaml around lines 50 - 57, The list calls for
closed issues and pulls use a single page (per_page: 100) and skip additional
pages; replace those single calls with paginated retrieval (use github.paginate
or an explicit page loop) to collect all results. Specifically, change the
github.rest.issues.listForRepo(...) and github.rest.pulls.list(...) calls to use
github.paginate(github.rest.issues.listForRepo, {...}) and
github.paginate(github.rest.pulls.list, {...}) (or a while/page increment loop)
with the same params (owner, repo, state, sort, direction, per_page) so you
aggregate all pages into a single array before filtering/locking.

Comment on lines +214 to +233
- name: Remove needs-triage when triaged
if: >-
github.event.action == 'labeled' &&
github.event.label.name != 'needs-triage'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
if (labels.some(l => l.name === 'needs-triage')) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'needs-triage',
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let unrelated labels satisfy triage.

This clears needs-triage for every non-needs-triage label. That includes automation like lifecycle/stale, so an untouched issue can stop looking triage-pending without any maintainer decision. Gate this branch on an explicit allow-list of triage-signaling labels instead of every label.

Suggested guard
       - name: Remove needs-triage when triaged
-        if: >-
-          github.event.action == 'labeled' &&
-          github.event.label.name != 'needs-triage'
+        if: github.event.action == 'labeled'
         uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3  # v9.0.0
         with:
           script: |
+            const TRIAGE_LABELS = new Set(['doc', 'duplicate', 'invalid', 'wontfix']);
+            const addedLabel = context.payload.label?.name;
+            if (
+              addedLabel === 'needs-triage' ||
+              (!addedLabel?.startsWith('component/') && !TRIAGE_LABELS.has(addedLabel))
+            ) {
+              return;
+            }
+
             const { data: labels } = await github.rest.issues.listLabelsOnIssue({
               owner: context.repo.owner,
               repo: context.repo.repo,
               issue_number: context.issue.number,
             });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Remove needs-triage when triaged
if: >-
github.event.action == 'labeled' &&
github.event.label.name != 'needs-triage'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
if (labels.some(l => l.name === 'needs-triage')) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'needs-triage',
});
}
- name: Remove needs-triage when triaged
if: github.event.action == 'labeled'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const TRIAGE_LABELS = new Set(['doc', 'duplicate', 'invalid', 'wontfix']);
const addedLabel = context.payload.label?.name;
if (
addedLabel === 'needs-triage' ||
(!addedLabel?.startsWith('component/') && !TRIAGE_LABELS.has(addedLabel))
) {
return;
}
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
if (labels.some(l => l.name === 'needs-triage')) {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'needs-triage',
});
}
🤖 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/triage.yaml around lines 214 - 233, The current workflow
clears 'needs-triage' whenever any non-'needs-triage' label is added; change the
logic to only clear 'needs-triage' when a triage-signaling label is explicitly
added. Replace the broad condition (github.event.label.name != 'needs-triage')
with a check against an allow-list/array of triage labels (e.g.,
ALLOWED_TRIAGE_LABELS) and only proceed when github.event.label.name is one of
those; inside the actions/github-script block also verify the added label is in
that allow-list before calling github.rest.issues.removeLabel (keeping the
existing labels list check for 'needs-triage') so automated labels like
'lifecycle/stale' won't clear triage status.

Comment on lines +45 to +47
const { data: { total_count } } = await github.rest.search.issuesAndPullRequests({
q: `repo:${context.repo.owner}/${context.repo.repo} type:issue author:${creator}`,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unify contribution counting to avoid duplicate “first-time contributor” welcomes.

Line 46 and Line 73 scope the search separately (type:issue vs type:pr), so someone can be welcomed once on their first issue and again on their first PR. If the intent is “first contribution in this repo,” both checks should query across issues and PRs.

Suggested patch
-            const { data: { total_count } } = await github.rest.search.issuesAndPullRequests({
-              q: `repo:${context.repo.owner}/${context.repo.repo} type:issue author:${creator}`,
-            });
+            const { data: { total_count } } = await github.rest.search.issuesAndPullRequests({
+              q: `repo:${context.repo.owner}/${context.repo.repo} author:${creator}`,
+            });
@@
-            const { data: { total_count } } = await github.rest.search.issuesAndPullRequests({
-              q: `repo:${context.repo.owner}/${context.repo.repo} type:pr author:${creator}`,
-            });
+            const { data: { total_count } } = await github.rest.search.issuesAndPullRequests({
+              q: `repo:${context.repo.owner}/${context.repo.repo} author:${creator}`,
+            });

Also applies to: 72-74

🤖 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/welcome.yaml around lines 45 - 47, The two separate
searches using github.rest.search.issuesAndPullRequests (one with `type:issue`
and another with `type:pr`) allow a user to be welcomed twice; change the
queries to count contributions across both issues and PRs by removing the
`type:` filter (or otherwise querying both kinds in one request) so the
`total_count` for `creator` reflects all contributions in the repo; update the
code paths that currently run the issue-only and pr-only searches to use this
unified search (references: github.rest.search.issuesAndPullRequests, the
`total_count` destructuring, and the `creator` variable).

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 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/inactive-pr-reminder.yaml:
- Around line 36-37: Add a short inline comment above the permissions block
explaining what the pull-requests: write scope allows; specifically, annotate
the permissions: section (near the pull-requests: write entry) with a one-line
comment stating that pull-requests: write permits the workflow to post comments
and perform write actions on PRs (e.g., add comments/labels), satisfying the
undocumented-permissions lint.
- Around line 68-76: The duplicate-reminder check only inspects the first page
returned by github.rest.issues.listComments and misses bot reminders on later
pages; replace that single-page call with a full-paginate call (use
github.paginate with github.rest.issues.listComments and the same params) to
retrieve all comments into the comments array, then run the existing
alreadyReminded check against the aggregated comments (referencing
github.paginate, github.rest.issues.listComments, comments, and
alreadyReminded).

In @.github/workflows/lock-threads.yaml:
- Around line 36-38: Add a short explanatory comment above the permissions block
to document why issues: write and pull-requests: write are required for the
workflow; specifically, note that these permissions allow the job to post
comments and lock threads (satisfying the undocumented-permissions lint and
matching repo convention). Locate the permissions stanza (the keys issues and
pull-requests) in the lock-threads workflow and insert a one-line comment
describing their purpose.

In @.github/workflows/triage.yaml:
- Around line 19-35: Add a concurrency block to the workflow to serialize
concurrent runs per-issue and prevent racing label events: in the workflow root
(the file defining the triage job) add a concurrency key using a per-issue group
like `${{ github.workflow }}-${{ github.event.issue.number }}` and set
`cancel-in-progress: true` so the `triage` job (job name `triage`) will not run
concurrently for the same issue when `opened`/`labeled` events fire.

In @.github/workflows/welcome.yaml:
- Around line 34-36: Permissions block currently lists issues: write and
pull-requests: write without context; add brief inline YAML comments above or
beside the keys explaining why each permission is required (e.g., "issues:
write" is needed for creating/closing issues or posting comments,
"pull-requests: write" is needed for updating PRs or adding labels) and keep
comments adjacent to the 'permissions', 'issues', and 'pull-requests' entries so
future maintainers and linters can understand the intent.
- Around line 19-24: Add a top-level concurrency stanza to the workflow to
serialize runs and cancel superseded ones; because this workflow is triggered by
issues, pull_request_target and workflow_dispatch, add a concurrency block using
a deterministic key like "welcome-${{ github.event.issue.number ||
github.event.pull_request.number || github.run_id }}" and set
cancel-in-progress: true so duplicate runs for the same issue/PR are cancelled.
Locate the workflow triggers (on: issues, pull_request_target,
workflow_dispatch) and insert the concurrency block adjacent to them.
🪄 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: ASSERTIVE

Plan: Enterprise

Run ID: 39915571-1879-4a63-81e0-2818e1fb020b

📥 Commits

Reviewing files that changed from the base of the PR and between a92da00 and 8c64bd8.

📒 Files selected for processing (11)
  • .github/ISSUE_TEMPLATE/bug_report_form.yml
  • .github/ISSUE_TEMPLATE/config.yml
  • .github/ISSUE_TEMPLATE/feature_request_form.yml
  • .github/labeler.yml
  • .github/labels.yml
  • .github/workflows/inactive-pr-reminder.yaml
  • .github/workflows/labeler.yaml
  • .github/workflows/lock-threads.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/triage.yaml
  • .github/workflows/welcome.yaml

Comment on lines +36 to +37
permissions:
pull-requests: write
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the pull-requests: write permission.

Add a brief comment explaining the write scope (commenting on PRs) to satisfy the undocumented-permissions lint.

📝 Proposed fix
     permissions:
+      # Needed to post reminder comments on inactive PRs.
       pull-requests: write
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 37-37: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 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/inactive-pr-reminder.yaml around lines 36 - 37, Add a
short inline comment above the permissions block explaining what the
pull-requests: write scope allows; specifically, annotate the permissions:
section (near the pull-requests: write entry) with a one-line comment stating
that pull-requests: write permits the workflow to post comments and perform
write actions on PRs (e.g., add comments/labels), satisfying the
undocumented-permissions lint.

Comment on lines +68 to +76
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
});
const alreadyReminded = comments.some(c =>
c.user.type === 'Bot' && c.body.includes('inactive for')
);
if (alreadyReminded) continue;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Duplicate-reminder guard only inspects the first page of comments.

listComments defaults to ~30 comments (oldest first) with no pagination. On a discussion-heavy PR, the bot's own recent reminder will sit on a later page, so alreadyReminded stays false and the PR gets re-pinged every day for the entire 14–29 day window. Fetch newest comments first (or paginate) for the dedup check.

🛠️ Proposed fix
-              const { data: comments } = await github.rest.issues.listComments({
-                owner: context.repo.owner,
-                repo: context.repo.repo,
-                issue_number: pr.number,
-              });
+              const comments = await github.paginate(github.rest.issues.listComments, {
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                issue_number: pr.number,
+                per_page: 100,
+              });
🤖 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/inactive-pr-reminder.yaml around lines 68 - 76, The
duplicate-reminder check only inspects the first page returned by
github.rest.issues.listComments and misses bot reminders on later pages; replace
that single-page call with a full-paginate call (use github.paginate with
github.rest.issues.listComments and the same params) to retrieve all comments
into the comments array, then run the existing alreadyReminded check against the
aggregated comments (referencing github.paginate,
github.rest.issues.listComments, comments, and alreadyReminded).

Comment on lines +36 to +38
permissions:
issues: write
pull-requests: write
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an explanatory comment for the job permissions.

issues: write / pull-requests: write are needed to comment on and lock threads; documenting them satisfies the undocumented-permissions lint and matches repo convention.

📝 Proposed fix
     permissions:
+      # Needed to comment on and lock closed issues/PRs.
       issues: write
       pull-requests: write
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
permissions:
issues: write
pull-requests: write
permissions:
# Needed to comment on and lock closed issues/PRs.
issues: write
pull-requests: write
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 37-37: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 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/lock-threads.yaml around lines 36 - 38, Add a short
explanatory comment above the permissions block to document why issues: write
and pull-requests: write are required for the workflow; specifically, note that
these permissions allow the job to post comments and lock threads (satisfying
the undocumented-permissions lint and matching repo convention). Locate the
permissions stanza (the keys issues and pull-requests) in the lock-threads
workflow and insert a one-line comment describing their purpose.

Comment on lines +19 to +35
on:
issues:
types: [opened, labeled]
workflow_dispatch: {}

permissions:
contents: read

jobs:

triage:
name: Auto-Triage Issues
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
timeout-minutes: 5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider adding concurrency to avoid racing label events.

The opened path adds needs-triage, then adds inferred component/* labels, then removes needs-triage. Each addLabels/label change emits a labeled event that re-triggers this same workflow concurrently, racing with the in-flight opened run. A per-issue concurrency group (e.g. ${{ github.workflow }}-${{ github.event.issue.number }}) would serialize these and cut redundant runs. Static analysis (zizmor concurrency-limits) also flags the missing setting.

♻️ Proposed addition
 on:
   issues:
     types: [opened, labeled]
   workflow_dispatch: {}

 permissions:
   contents: read

+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.issue.number }}
+  cancel-in-progress: false
+
 jobs:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on:
issues:
types: [opened, labeled]
workflow_dispatch: {}
permissions:
contents: read
jobs:
triage:
name: Auto-Triage Issues
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
timeout-minutes: 5
on:
issues:
types: [opened, labeled]
workflow_dispatch: {}
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: false
jobs:
triage:
name: Auto-Triage Issues
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
timeout-minutes: 5
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 34-34: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)


[warning] 19-22: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 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/triage.yaml around lines 19 - 35, Add a concurrency block
to the workflow to serialize concurrent runs per-issue and prevent racing label
events: in the workflow root (the file defining the triage job) add a
concurrency key using a per-issue group like `${{ github.workflow }}-${{
github.event.issue.number }}` and set `cancel-in-progress: true` so the `triage`
job (job name `triage`) will not run concurrently for the same issue when
`opened`/`labeled` events fire.

Comment on lines +19 to +24
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
workflow_dispatch: {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a concurrency group to prevent redundant parallel runs.

Without a concurrency setting, rapid issue/PR creation can spawn many parallel workflow runs, risking GitHub API rate limits. Adding a concurrency group serializes runs per issue/PR and cancels superseded runs.

Note: The pull_request_target trigger flagged by static analysis is safe here since this workflow never checks out or executes PR code—it only reads metadata and posts comments.

Suggested fix
 on:
   issues:
     types: [opened]
   pull_request_target:
     types: [opened]
   workflow_dispatch: {}
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}
+  cancel-in-progress: true
+
 permissions:
   contents: read
🧰 Tools
🪛 zizmor (1.25.2)

[error] 19-24: use of fundamentally insecure workflow trigger (dangerous-triggers): pull_request_target is almost always used insecurely

(dangerous-triggers)


[warning] 19-24: insufficient job-level concurrency limits (concurrency-limits): workflow is missing concurrency setting

(concurrency-limits)

🤖 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/welcome.yaml around lines 19 - 24, Add a top-level
concurrency stanza to the workflow to serialize runs and cancel superseded ones;
because this workflow is triggered by issues, pull_request_target and
workflow_dispatch, add a concurrency block using a deterministic key like
"welcome-${{ github.event.issue.number || github.event.pull_request.number ||
github.run_id }}" and set cancel-in-progress: true so duplicate runs for the
same issue/PR are cancelled. Locate the workflow triggers (on: issues,
pull_request_target, workflow_dispatch) and insert the concurrency block
adjacent to them.

Comment on lines +34 to +36
permissions:
issues: write
pull-requests: write
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding comments explaining permission requirements.

Brief inline comments clarify why each permission is needed, aiding future maintainers and satisfying linter recommendations.

Suggested improvement
     permissions:
-      issues: write
-      pull-requests: write
+      issues: write        # required to post welcome comments on issues
+      pull-requests: write # required to post welcome comments on PRs
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 35-35: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 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/welcome.yaml around lines 34 - 36, Permissions block
currently lists issues: write and pull-requests: write without context; add
brief inline YAML comments above or beside the keys explaining why each
permission is required (e.g., "issues: write" is needed for creating/closing
issues or posting comments, "pull-requests: write" is needed for updating PRs or
adding labels) and keep comments adjacent to the 'permissions', 'issues', and
'pull-requests' entries so future maintainers and linters can understand the
intent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add GitHub issue/PR-management automation (triage, labeler, welcome, lock-threads, inactive-pr-reminder)

2 participants