Skip to content
Merged
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
182 changes: 182 additions & 0 deletions .github/actions/stale-check/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Stale Issues and PRs Action

A composite GitHub Action that wraps [actions/stale](https://github.com/actions/stale) to automatically mark inactive issues and pull requests as stale and eventually close them. This keeps the issue tracker focused on active work.

## How It Works

```
Issue opened ──► 60 days idle ──► Labeled "lifecycle/stale" ──► 14 more days ──► Closed
PR opened ──► 30 days idle ──► Labeled "lifecycle/stale" ──► 14 more days ──► Closed
```

### Default Exemptions

| Label | Effect |
| --- | --- |
| `lifecycle/frozen` | Never marked stale (issues and PRs) |
| `priority/critical` | Never marked stale (issues only) |
| `good first issue` | Never marked stale (issues only) |
| `do-not-merge` | Never marked stale (PRs only) |

## Quick Start

```yaml
name: Stale Issues and PRs

on:
schedule:
- cron: "0 6 * * *" # Daily at 6am UTC
workflow_dispatch: {}

permissions:
contents: read

jobs:
stale:
name: Mark stale issues and PRs
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
timeout-minutes: 10
steps:
- uses: NVIDIA/dsx-github-actions/.github/actions/stale-check@main
```

All inputs have sensible defaults, so zero configuration is needed to get started.

## Usage

### Custom Timelines

```yaml
- uses: NVIDIA/dsx-github-actions/.github/actions/stale-check@main
with:
days-before-issue-stale: '90'
days-before-pr-stale: '45'
days-before-close: '7'
```

### Custom Exempt Labels

```yaml
- uses: NVIDIA/dsx-github-actions/.github/actions/stale-check@main
with:
exempt-issue-labels: 'lifecycle/frozen,priority/critical,epic'
exempt-pr-labels: 'lifecycle/frozen,do-not-merge,wip'
```

### Custom Messages

```yaml
- uses: NVIDIA/dsx-github-actions/.github/actions/stale-check@main
with:
stale-issue-message: |
This issue has been inactive for 60 days and will be closed soon.
Please comment to keep it open.
stale-pr-message: |
This PR has been inactive for 30 days and will be closed soon.
Please push commits or comment to keep it open.
```

## Inputs

| Input | Description | Required | Default |
| --- | --- | --- | --- |
| `days-before-issue-stale` | Days of inactivity before an issue is marked stale | No | `60` |
| `days-before-pr-stale` | Days of inactivity before a PR is marked stale | No | `30` |
| `days-before-close` | Days after stale label before closing | No | `14` |
| `stale-issue-label` | Label to apply to stale issues | No | `lifecycle/stale` |
| `stale-pr-label` | Label to apply to stale PRs | No | `lifecycle/stale` |
| `exempt-issue-labels` | Comma-separated labels that exempt issues from being marked stale | No | `lifecycle/frozen,priority/critical,good first issue` |
| `exempt-pr-labels` | Comma-separated labels that exempt PRs from being marked stale | No | `lifecycle/frozen,do-not-merge` |
| `stale-issue-message` | Message posted when an issue is marked stale | No | *(see default below)* |
| `stale-pr-message` | Message posted when a PR is marked stale | No | *(see default below)* |
| `close-issue-message` | Message posted when a stale issue is closed | No | *(see default below)* |
| `close-pr-message` | Message posted when a stale PR is closed | No | *(see default below)* |
| `operations-per-run` | Maximum number of operations per run (API budget) | No | `500` |

### Default Messages

**Stale issue message:**

> This issue has been marked as stale due to 60 days of inactivity.
> It will be closed in 14 days unless there is new activity.
>
> To keep this issue open:
>
> - Add a comment with an update
> - Add the `lifecycle/frozen` label
>
> If this issue is no longer relevant, it's okay to let it close.

**Stale PR message:**

> This PR has been marked as stale due to 30 days of inactivity.
> It will be closed in 14 days unless there is new activity.
>
> To keep this PR open:
>
> - Push new commits or add a comment
> - Add the `lifecycle/frozen` label
>
> If this work is no longer needed, it's okay to let it close.

**Close issue message:**

> This issue was automatically closed due to inactivity after being marked as stale.
>
> If you believe this issue is still relevant:
>
> - Reopen with a comment providing updated details or context
> - Add the `lifecycle/frozen` label to prevent it from being closed again

**Close PR message:**

> This pull request was automatically closed due to inactivity after being marked as stale.
>
> If you would like to continue this work:
>
> - Reopen and push new commits or add a comment with an update
> - Add the `lifecycle/frozen` label to prevent it from being closed again

## Required Permissions

The calling workflow job must have:

```yaml
permissions:
issues: write
pull-requests: write
```

## Recommended Labels

Create these labels in your repository for full functionality:

| Label | Description | Color suggestion |
| --- | --- | --- |
| `lifecycle/stale` | Automatically applied to inactive issues/PRs | `#795548` |
| `lifecycle/frozen` | Prevents an issue/PR from being marked stale | `#0d47a1` |
| `priority/critical` | Exempts issues from stale checks | `#d32f2f` |
| `do-not-merge` | Exempts PRs from stale checks | `#b71c1c` |

## Version Pinning

### Use Specific Version (Recommended for Production)

```yaml
uses: NVIDIA/dsx-github-actions/.github/actions/stale-check@v1.0.0
```

### Use Latest (Development/Testing)

```yaml
uses: NVIDIA/dsx-github-actions/.github/actions/stale-check@main
```

## License

Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.

Licensed under the Apache License, Version 2.0.
131 changes: 131 additions & 0 deletions .github/actions/stale-check/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
#
# 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.

# Stale Issue and PR Management
#
# Automatically marks inactive issues/PRs as stale and eventually closes them.
# This keeps the issue tracker focused on active work.
#
# Timelines (defaults):
# Issues: 60 days -> stale, 14 more days -> closed
# PRs: 30 days -> stale, 14 more days -> closed
#
# Exemptions (defaults):
# - lifecycle/frozen: Never goes stale
# - priority/critical: Never goes stale (issues only)
# - do-not-merge: PR won't be closed
#
# Required permissions in calling workflow:
# issues: write
# pull-requests: write
#
# Pattern from: gpu-operator

name: 'Stale Issues and PRs'
description: 'Marks inactive issues and PRs as stale and eventually closes them to keep the tracker focused on active work'

inputs:
days-before-issue-stale:
description: 'Days of inactivity before an issue is marked stale'
required: false
default: '60'
days-before-pr-stale:
description: 'Days of inactivity before a PR is marked stale'
required: false
default: '30'
days-before-close:
description: 'Days after stale label before closing'
required: false
default: '14'
stale-issue-label:
description: 'Label to apply to stale issues'
required: false
default: 'lifecycle/stale'
stale-pr-label:
description: 'Label to apply to stale PRs'
required: false
default: 'lifecycle/stale'
exempt-issue-labels:
description: 'Comma-separated labels that exempt issues from being marked stale'
required: false
default: 'lifecycle/frozen,priority/critical,good first issue'
exempt-pr-labels:
description: 'Comma-separated labels that exempt PRs from being marked stale'
required: false
default: 'lifecycle/frozen,do-not-merge'
stale-issue-message:
description: 'Message posted when an issue is marked stale'
required: false
default: |
This issue has been marked as stale due to 60 days of inactivity.
It will be closed in 14 days unless there is new activity.

To keep this issue open:
- Add a comment with an update
- Add the `lifecycle/frozen` label

If this issue is no longer relevant, it's okay to let it close.
stale-pr-message:
description: 'Message posted when a PR is marked stale'
required: false
default: |
This PR has been marked as stale due to 30 days of inactivity.
It will be closed in 14 days unless there is new activity.

To keep this PR open:
- Push new commits or add a comment
- Add the `lifecycle/frozen` label

If this work is no longer needed, it's okay to let it close.
close-issue-message:
description: 'Message posted when a stale issue is closed'
required: false
default: |
This issue was automatically closed due to inactivity after being marked as stale.

If you believe this issue is still relevant:
- Reopen with a comment providing updated details or context
- Add the `lifecycle/frozen` label to prevent it from being closed again
close-pr-message:
description: 'Message posted when a stale PR is closed'
required: false
default: |
This pull request was automatically closed due to inactivity after being marked as stale.

If you would like to continue this work:
- Reopen and push new commits or add a comment with an update
- Add the `lifecycle/frozen` label to prevent it from being closed again
operations-per-run:
description: 'Maximum number of operations per run (API budget)'
required: false
default: '500'

runs:
using: 'composite'
steps:
- name: Mark stale issues and PRs
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
stale-issue-message: ${{ inputs.stale-issue-message }}
days-before-stale: ${{ inputs.days-before-issue-stale }}
stale-issue-label: ${{ inputs.stale-issue-label }}
exempt-issue-labels: ${{ inputs.exempt-issue-labels }}
stale-pr-message: ${{ inputs.stale-pr-message }}
days-before-pr-stale: ${{ inputs.days-before-pr-stale }}
stale-pr-label: ${{ inputs.stale-pr-label }}
exempt-pr-labels: ${{ inputs.exempt-pr-labels }}
close-issue-message: ${{ inputs.close-issue-message }}
close-pr-message: ${{ inputs.close-pr-message }}
days-before-close: ${{ inputs.days-before-close }}
operations-per-run: ${{ inputs.operations-per-run }}