mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-25 16:44:36 +00:00
Fix stale bot re-commenting on case-mismatched labels (#4394)
## Problem The issue-lifecycle stale rule checked labels with case-sensitive `Array.includes()`, so an issue carrying the `Stale` label (created by the `actions/stale` PR bot) was never recognized as stale. `hasStaleLabel` stayed `false` and the bot re-posted the 7-day warning on **every** daily cron run. Example: [#3441](https://github.com/openfrontio/OpenFrontIO/issues/3441) got the same "hasn't had activity in 7 days" comment ~16 days in a row. ## Fix GitHub label names are case-insensitive (you can't have both `Stale` and `stale`), so the gate should be too. Adds a `hasLabel()` helper in `github.ts` and routes all label checks through it (`STALE`, `KEEP_OPEN`, `APPROVED`, `NOT_APPROVED`). Now an issue gets one stale warning when marked, then silence until the 14-day close. ## Note The Dependabot PR-exemption change (`pr-stale.yml`) is being applied separately — the CI token here lacks `workflow` scope to push workflow-file changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,14 @@ export function makeOctokit(token: string): Octokit {
|
||||
return new Octokit({ auth: token });
|
||||
}
|
||||
|
||||
// GitHub label names are case-insensitive (you cannot have both "Stale" and
|
||||
// "stale"), so match them that way — otherwise a label applied with different
|
||||
// casing (e.g. the "Stale" label from the PR stale action) is missed.
|
||||
export function hasLabel(issue: Issue, label: string): boolean {
|
||||
const target = label.toLowerCase();
|
||||
return issue.labels.some((l) => l.toLowerCase() === target);
|
||||
}
|
||||
|
||||
export function isBotUser(
|
||||
user: { login: string; type: string } | null,
|
||||
): boolean {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LABELS } from "../config";
|
||||
import type { Action, Issue } from "../github";
|
||||
import { type Action, hasLabel, type Issue } from "../github";
|
||||
|
||||
export function syncApprovalLabel(issue: Issue): Action[] {
|
||||
const hasApproved = issue.labels.includes(LABELS.APPROVED);
|
||||
const hasNotApproved = issue.labels.includes(LABELS.NOT_APPROVED);
|
||||
const hasApproved = hasLabel(issue, LABELS.APPROVED);
|
||||
const hasNotApproved = hasLabel(issue, LABELS.NOT_APPROVED);
|
||||
const milestoned = issue.milestone !== null;
|
||||
const actions: Action[] = [];
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type Action,
|
||||
type Issue,
|
||||
type IssueComment,
|
||||
hasLabel,
|
||||
isBotUser,
|
||||
listIssueComments,
|
||||
} from "../github";
|
||||
@@ -35,12 +36,12 @@ export async function checkStale(
|
||||
now: Date = new Date(),
|
||||
): Promise<Action[]> {
|
||||
if (issue.milestone !== null) return [];
|
||||
if (issue.labels.includes(LABELS.KEEP_OPEN)) return [];
|
||||
if (hasLabel(issue, LABELS.KEEP_OPEN)) return [];
|
||||
|
||||
const comments = await listIssueComments(octokit, issue.number);
|
||||
const lastActivityIso = latestNonBotActivityIso(issue, comments);
|
||||
const daysSinceActivity = daysBetween(lastActivityIso, now);
|
||||
const hasStaleLabel = issue.labels.includes(LABELS.STALE);
|
||||
const hasStaleLabel = hasLabel(issue, LABELS.STALE);
|
||||
const authorLogin = issue.user?.login ?? "there";
|
||||
|
||||
if (hasStaleLabel && daysSinceActivity < STALE_WARN_DAYS) {
|
||||
|
||||
Reference in New Issue
Block a user