Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report_form.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
name: Bug Report
description: File a bug report for Skyhook
title: "[BUG]: "
labels: ["bug"]
type: Bug
labels: ["needs-triage"]

body:
- type: markdown
Expand Down Expand Up @@ -55,6 +56,24 @@ body:
validations:
required: true

- type: dropdown
id: component
attributes:
label: Component
description: Which part(s) of the project are affected? Select all that apply; this helps us route the issue.
multiple: true
options:
- Operator (controller-manager)
- Agent (package executor)
- Helm chart
- CLI (kubectl-skyhook)
- CI / tooling
- Tests (e2e / chainsaw)
- Docs / examples
- Other / Unknown
validations:
required: true

- type: textarea
id: description
attributes:
Expand Down Expand Up @@ -94,9 +113,9 @@ body:
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/NVIDIA/skyhook/blob/main/CODE_OF_CONDUCT.md).
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/NVIDIA/nodewright/blob/main/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow Skyhook's Code of Conduct
required: true
- label: I have searched the [open bugs](https://github.com/NVIDIA/skyhook/issues?q=is%3Aopen+is%3Aissue+label%3Abug) and have found no duplicates for this bug report
- label: I have searched the [open bugs](https://github.com/NVIDIA/nodewright/issues?q=is%3Aopen+is%3Aissue+type%3ABug) and have found no duplicates for this bug report
required: true
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ blank_issues_enabled: true

contact_links:
- name: Ask a Question
url: https://github.com/NVIDIA/skyhook/discussions
url: https://github.com/NVIDIA/nodewright/discussions
about: Please ask any questions here.
- name: Report a Security Vulnerability
url: https://www.nvidia.com/object/submit-security-vulnerability.html
Expand Down
25 changes: 22 additions & 3 deletions .github/ISSUE_TEMPLATE/feature_request_form.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
name: Feature Request
description: Request new or improved functionality
title: "[FEA]: "
labels: ["feature"]
type: Enhancement
labels: ["needs-triage"]

body:
- type: markdown
Expand Down Expand Up @@ -49,6 +50,24 @@ body:
validations:
required: true

- type: dropdown
id: component
attributes:
label: Component
description: Which part(s) of the project would this affect? Select all that apply; this helps us route the request.
multiple: true
options:
- Operator (controller-manager)
- Agent (package executor)
- Helm chart
- CLI (kubectl-skyhook)
- CI / tooling
- Tests (e2e / chainsaw)
- Docs / examples
- Other / Unknown
validations:
required: true

- type: textarea
id: problem
attributes:
Expand Down Expand Up @@ -91,9 +110,9 @@ body:
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/NVIDIA/skyhook/blob/main/CODE_OF_CONDUCT.md).
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/NVIDIA/nodewright/blob/main/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow Skyhook's Code of Conduct
required: true
- label: I have searched the [open feature requests](https://github.com/NVIDIA/skyhook/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22) and have found no duplicates for this feature request
- label: I have searched the [open feature requests](https://github.com/NVIDIA/nodewright/issues?q=is%3Aopen+is%3Aissue+type%3AEnhancement) and have found no duplicates for this feature request
required: true
65 changes: 65 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed 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.

# Path-based PR labels for actions/labeler. Kept in sync with the component/*
# label family in .github/labels.yml and the triage keyword maps in
# .github/workflows/triage.yaml.

# 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/**'

Comment on lines +21 to +28
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.

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

component/agent:
- changed-files:
- any-glob-to-any-file:
- 'agent/**'

component/chart:
- changed-files:
- any-glob-to-any-file:
- 'chart/**'

component/ci:
- changed-files:
- any-glob-to-any-file:
- '.github/**'
- 'scripts/**'
- '.gitlab-ci.yml'
- '**/Makefile'
- '**/*.mk'
# Don't let vendored third-party Makefiles trip the CI label.
- all-globs-to-all-files:
- '!**/vendor/**'

component/tests:
- changed-files:
- any-glob-to-any-file:
- 'k8s-tests/**'

doc:
- changed-files:
- any-glob-to-any-file:
- 'docs/**'
- '**/*.md'
91 changes: 33 additions & 58 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,83 +19,58 @@
# Sync to GitHub with: make labels
#
# Label categories:
# Issue types — applied automatically by issue templates
# Component — which part of the project is affected
# Planning — applied by engineering/product during triage (per DGXC OSS Policy)
# Priority — P0/P1/P2 per DGXC OSS Policy, applied by engineering
# Lifecycle — stale automation and freeze exemptions
# Standard — common GitHub labels

# ── Issue Types ───────────────────────────────────────────────────────────────
# Applied automatically by issue templates

- name: "bug"
color: "d73a4a"
description: "Something isn't working"

- name: "feature request"
color: "a2eeef"
description: "New feature or improvement to existing functionality"
# Type: issue Type (Bug/Enhancement/Documentation/Task/Epic/Initiative) is
# a native GitHub Issue Type set by the org, not a label
# Triage: needs-triage, cleared once a maintainer triages
# Component: which part of the project is affected
# Lifecycle: stale automation and freeze exemptions
# Standard: common GitHub labels

# ── Type ──────────────────────────────────────────────────────────────────────
# Bug / Enhancement / Documentation / Task / Epic / Initiative are native GitHub
# Issue Types (org-level), assigned by the issue forms via their `type:` key, not
# labels. `doc` stays as a label because the PR path labeler applies it to
# docs-only PRs (issues use the Documentation type instead).

- name: "doc"
color: "0075ca"
description: "Documentation addition, correction, or improvement"
description: "Documentation change (PR path label; doc issues use the Documentation type)"

# ── Triage ───────────────────────────────────────────────────────────────────
# Applied automatically to new issues; removed once a maintainer triages.

- name: "needs-triage"
color: "fbca04"
description: "Awaiting initial triage by a maintainer"

# ── Component ────────────────────────────────────────────────────────────────
# Which part of the project is affected
# Which part of the project is affected. Applied automatically by issue triage
# and PR path labeling. Each component gets a distinct hue so they pop apart at
# a glance; the "component/" prefix keeps them grouped in label lists.

- name: "component/operator"
color: "1d76db"
color: "0969da"
description: "Skyhook operator (controller-manager)"

- name: "component/agent"
color: "1d76db"
color: "1a7f37"
description: "Skyhook agent (package executor)"

- name: "component/chart"
color: "1d76db"
color: "8250df"
description: "Helm chart"

- name: "component/cli"
color: "1d76db"
color: "bc4c00"
description: "kubectl-skyhook CLI plugin"

# ── Planning Types ────────────────────────────────────────────────────────────
# Applied by engineering/product during triage (DGXC OSS Policy issue types)

- name: "epic"
color: "7057ff"
description: "Roadmap feature — large body of work spanning multiple issues"

- name: "feature"
color: "a2eeef"
description: "Goal of a feature request or enhancement (planning label)"

- name: "task"
color: "e4e669"
description: "Sub-task of a feature or epic"

- name: "initiative"
color: "f9d0c4"
description: "Container for a group of related features (optional)"

# ── Priority ─────────────────────────────────────────────────────────────────
# Set by engineering only. SLAs per DGXC OSS Policy:
# P0: major functionality broken, no workaround — 30 day fix SLA
# P1: product works but not as expected, workaround exists — 6 month SLA
# P2: minor defect, minor impact — no fix commitment

- name: "P0"
color: "b60205"
description: "Critical: major functionality broken, no workaround. 30-day SLA."

- name: "P1"
color: "e11d48"
description: "High: product works but not as expected. Workaround exists. 6-month SLA."
- name: "component/ci"
color: "bf3989"
description: "CI workflows, GitHub Actions, and repo tooling"

- name: "P2"
color: "f97316"
description: "Medium: minor defect or minor impact. No fix commitment."
- name: "component/tests"
color: "9a6700"
description: "End-to-end / chainsaw test suites (k8s-tests)"

# ── Lifecycle ─────────────────────────────────────────────────────────────────
# Used by stale automation
Expand Down
84 changes: 84 additions & 0 deletions .github/workflows/inactive-pr-reminder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed 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: Inactive PR Reminder

on:
schedule:
- cron: '0 7 * * *' # daily at 07:00 UTC
workflow_dispatch: {}

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}

jobs:

remind:
name: Nudge Inactive PRs
if: github.repository == 'NVIDIA/nodewright'
runs-on: ubuntu-latest
permissions:
pull-requests: write
Comment on lines +36 to +37
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.

timeout-minutes: 10
steps:
- name: Check for inactive PRs
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const now = new Date();
const msPerDay = 86400000;
const skipLabels = ['lifecycle/frozen', 'do-not-merge'];

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 on lines +48 to +55
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.


for (const pr of prs) {
// Skip PRs explicitly held open or parked.
if (pr.labels.some(l => skipLabels.includes(l.name))) continue;

const updated = new Date(pr.updated_at);
const daysInactive = Math.floor((now - updated) / msPerDay);

// Target the 14–29 day window, before the stale bot fires at 30.
if (daysInactive < 14 || daysInactive >= 30) continue;

// Don't double-remind: skip if a bot already left a reminder.
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;
Comment on lines +68 to +76
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).


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.`,
});
}
Loading
Loading