From 134dbaecd7b80a4d5b24aab9cd55b9c4088dacd2 Mon Sep 17 00:00:00 2001 From: Chris Nienhuis Date: Tue, 4 Aug 2026 10:04:30 -0500 Subject: [PATCH 1/2] feat: add ignore-bot-updates option When enabled, activity caused by bot accounts (user type Bot) does not remove the stale label and does not block closing. Human activity behaves as usual: comments, label changes, and (for pull requests) pushed commits remove the stale label and restart the cycle. Motivation: in repositories with heavy automation (label syncs, preview deployments, generated comments), every bot touch bumps updated_at, removes the stale label, and restarts the full days-before-stale countdown - so the stale cycle never completes and PRs orbit the warning state indefinitely. Related: #795, #1309, #1243. Behavior details: - The un-stale check attributes updates via issue events: if every event since the stale marking was caused by a bot (or was the stale labeling itself), the update does not un-stale the item. - For pull requests, the last commit date is checked so a human push (which bumps updated_at without leaving an issue event) still counts as human activity. - An updated_at bump with no attributable cause (e.g. a body edit) is treated as human activity, keeping the item open - fail-safe direction. - The close-window check runs from the stale-marking date instead of updated_at, since bots keep updated_at perpetually fresh. - Default false; behavior is byte-identical when disabled (covered by a regression test mirroring the enabled-path fixture). --- README.md | 12 + .../constants/default-processor-options.ts | 1 + __tests__/ignore-bot-updates.spec.ts | 241 ++++++++++++++++++ action.yml | 4 + dist/index.js | 70 ++++- src/classes/issue.spec.ts | 1 + src/classes/issues-processor.ts | 123 ++++++++- src/enums/option.ts | 1 + src/interfaces/issue-event.ts | 2 + src/interfaces/issues-processor-options.ts | 1 + src/main.ts | 1 + 11 files changed, 438 insertions(+), 19 deletions(-) create mode 100644 __tests__/ignore-bot-updates.spec.ts diff --git a/README.md b/README.md index e509b285d..42b47b4ac 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,7 @@ Every argument is optional. | [ignore-updates](#ignore-updates) | Any update (update/comment) can reset the stale idle time on the issues/PRs | `false` | | [ignore-issue-updates](#ignore-issue-updates) | Override [ignore-updates](#ignore-updates) for issues only | | | [ignore-pr-updates](#ignore-pr-updates) | Override [ignore-updates](#ignore-updates) for PRs only | | +| [ignore-bot-updates](#ignore-bot-updates) | Bot activity does not reset the stale clock or block closing | `false` | | [include-only-assigned](#include-only-assigned) | Process only assigned issues | `false` | | [sort-by](#sort-by) | What to sort issues and PRs by | `created` | | [only-issue-types](#only-issue-types) | Only issues with a matching type are processed as stale/closed. | | @@ -555,6 +556,17 @@ Useful to override [ignore-updates](#ignore-updates) but only to ignore the upda Default value: unset +#### ignore-bot-updates + +If set to `true`, activity caused by bot accounts (users of type `Bot`) does not remove the stale label and does not block closing. +This covers bot comments, bot label changes, and other bot-caused events. +Human activity behaves as usual: a comment, label change, or (for pull requests) a pushed commit removes the stale label and restarts the cycle. +An `updated_at` change that cannot be attributed (for example a body edit, which leaves no event) is treated as human activity, so the action errs on the side of keeping items open. + +Useful for repositories where automation (label syncs, preview deployments, generated comments) updates issues and pull requests so often that the stale cycle never completes. + +Default value: `false` + #### include-only-assigned If set to `true`, only the issues or the pull requests with an assignee will be marked as stale automatically. diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index 48fb51b11..b45f1bcff 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -52,6 +52,7 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ labelsToRemoveWhenUnstale: '', labelsToAddWhenUnstale: '', ignoreUpdates: false, + ignoreBotUpdates: false, ignoreIssueUpdates: undefined, ignorePrUpdates: undefined, exemptDraftPr: false, diff --git a/__tests__/ignore-bot-updates.spec.ts b/__tests__/ignore-bot-updates.spec.ts new file mode 100644 index 000000000..8bb0e945f --- /dev/null +++ b/__tests__/ignore-bot-updates.spec.ts @@ -0,0 +1,241 @@ +import {expect, test} from '@jest/globals'; +import {Issue} from '../src/classes/issue.js'; +import {IIssueEvent} from '../src/interfaces/issue-event.js'; +import {IssuesProcessorMock} from './classes/issues-processor-mock.js'; +import {DefaultProcessorOptions} from './constants/default-processor-options.js'; +import {generateIssue} from './functions/generate-issue.js'; +import {alwaysFalseStateMock} from './classes/state-mock.js'; + +const millisPerDay = 1000 * 60 * 60 * 24; + +function daysAgo(days: number): string { + return new Date(Date.now() - days * millisPerDay).toISOString(); +} + +const botLabeledEvent = (createdAt: string, label: string): IIssueEvent => ({ + created_at: createdAt, + event: 'labeled', + label: {name: label}, + actor: {login: 'some-app[bot]', type: 'Bot'} +}); + +const botCommentedEvent = (createdAt: string): IIssueEvent => ({ + created_at: createdAt, + event: 'commented', + label: {name: ''}, + actor: {login: 'some-app[bot]', type: 'Bot'} +}); + +const humanLabeledEvent = (createdAt: string, label: string): IIssueEvent => ({ + created_at: createdAt, + event: 'labeled', + label: {name: label}, + actor: {login: 'octocat', type: 'User'} +}); + +test('bot activity does not remove the stale label when ignore-bot-updates is enabled', async () => { + expect.assertions(2); + const opts = { + ...DefaultProcessorOptions, + ignoreBotUpdates: true, + daysBeforeClose: 7, + removeStaleWhenUpdated: true + }; + const markedStaleOn = daysAgo(2); + // A bot commented after the stale marking: updated_at is fresh. + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A stale issue touched only by bots', + daysAgo(1), // updated_at bumped by the bot + daysAgo(40), + false, + false, + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => ({ + creationDate: markedStaleOn, + events: [ + botLabeledEvent(markedStaleOn, 'Stale'), + botCommentedEvent(daysAgo(1)) + ] + }) + ); + + await processor.processIssues(1); + + expect(processor.removedLabelIssues).toHaveLength(0); + expect(processor.closedIssues).toHaveLength(0); // warned 2d ago; close window is 7d +}); + +test('stale issue closes despite recent bot activity when ignore-bot-updates is enabled', async () => { + expect.assertions(1); + const opts = { + ...DefaultProcessorOptions, + ignoreBotUpdates: true, + daysBeforeClose: 7, + removeStaleWhenUpdated: true + }; + const markedStaleOn = daysAgo(10); // warned 10d ago > 7d close window + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A stale issue that bots keep touching', + daysAgo(1), // bots keep updated_at perpetually fresh + daysAgo(60), + false, + false, + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => ({ + creationDate: markedStaleOn, + events: [ + botLabeledEvent(markedStaleOn, 'Stale'), + botCommentedEvent(daysAgo(3)), + botCommentedEvent(daysAgo(1)) + ] + }) + ); + + await processor.processIssues(1); + + expect(processor.closedIssues).toHaveLength(1); +}); + +test('human labeling still removes the stale label when ignore-bot-updates is enabled', async () => { + expect.assertions(2); + const opts = { + ...DefaultProcessorOptions, + ignoreBotUpdates: true, + daysBeforeClose: 7, + removeStaleWhenUpdated: true + }; + const markedStaleOn = daysAgo(10); + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A stale issue a human labeled after the marking', + daysAgo(1), + daysAgo(60), + false, + false, + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => ({ + creationDate: markedStaleOn, + events: [ + botLabeledEvent(markedStaleOn, 'Stale'), + humanLabeledEvent(daysAgo(1), 'priority') + ] + }) + ); + + await processor.processIssues(1); + + expect(processor.removedLabelIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); +}); + +test('a commit pushed after the stale marking blocks closing a PR when ignore-bot-updates is enabled', async () => { + expect.assertions(2); + const opts = { + ...DefaultProcessorOptions, + ignoreBotUpdates: true, + daysBeforeClose: 7, + removeStaleWhenUpdated: true + }; + const markedStaleOn = daysAgo(10); + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A stale PR whose author pushed yesterday', + daysAgo(1), + daysAgo(60), + false, + true, // pull request + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => ({ + creationDate: markedStaleOn, + events: [botLabeledEvent(markedStaleOn, 'Stale')] + }) + ); + processor.getPullRequestLastCommitDate = async () => daysAgo(1); + + await processor.processIssues(1); + + // The push counts as a human update: un-stale, don't close. + expect(processor.removedLabelIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); +}); + +test('default behavior is unchanged when ignore-bot-updates is disabled', async () => { + expect.assertions(2); + const opts = { + ...DefaultProcessorOptions, + ignoreBotUpdates: false, + daysBeforeClose: 7, + removeStaleWhenUpdated: true + }; + const markedStaleOn = daysAgo(10); + // Same shape as the "closes despite bots" test above — but with the option + // off, the bot comment resets everything, exactly as before. + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A stale issue that bots keep touching', + daysAgo(1), + daysAgo(60), + false, + false, + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => ({ + creationDate: markedStaleOn, + events: [ + botLabeledEvent(markedStaleOn, 'Stale'), + botCommentedEvent(daysAgo(1)) + ] + }) + ); + + await processor.processIssues(1); + + expect(processor.removedLabelIssues).toHaveLength(1); // updated_at bump un-stales + expect(processor.closedIssues).toHaveLength(0); +}); diff --git a/action.yml b/action.yml index b3354e9d5..3e2050b89 100644 --- a/action.yml +++ b/action.yml @@ -204,6 +204,10 @@ inputs: description: 'Any update (update/comment) can reset the stale idle time on the pull requests. Override "ignore-updates" option regarding only the pull requests.' default: '' required: false + ignore-bot-updates: + description: 'Updates, comments, and label changes made by bot accounts (user type "Bot") do not remove the stale label and do not block closing. Human activity behaves as usual.' + default: 'false' + required: false include-only-assigned: description: 'Only the issues or the pull requests with an assignee will be marked as stale automatically.' default: 'false' diff --git a/dist/index.js b/dist/index.js index 3171f8fb5..cad12ef75 100644 --- a/dist/index.js +++ b/dist/index.js @@ -49473,6 +49473,7 @@ var Option; Option["IgnoreUpdates"] = "ignore-updates"; Option["IgnoreIssueUpdates"] = "ignore-issue-updates"; Option["IgnorePrUpdates"] = "ignore-pr-updates"; + Option["IgnoreBotUpdates"] = "ignore-bot-updates"; Option["ExemptDraftPr"] = "exempt-draft-pr"; Option["CloseIssueReason"] = "close-issue-reason"; Option["OnlyIssueTypes"] = "only-issue-types"; @@ -51920,7 +51921,7 @@ class IssuesProcessor { } async hasOnlyStaleLabelingEventsSince(issue, sinceDate, staleLabel, events) { const issueLogger = new IssueLogger(issue); - issueLogger.info(`Checking if only stale label added events on $$type since: ${LoggerService.cyan(sinceDate)}`); + issueLogger.info(`Checking if only ignorable events on $$type since: ${LoggerService.cyan(sinceDate)}`); if (!sinceDate) { return false; } @@ -51936,12 +51937,61 @@ class IssuesProcessor { return false; } return relevantEvents.every(event => { + // With ignore-bot-updates, any event caused by a bot account is + // ignorable — label syncs, automated milestoning, etc. + if (this.options.ignoreBotUpdates && event.actor?.type === 'Bot') { + return true; + } if (event.event !== 'labeled') { return false; } return cleanLabel(event.label.name) === cleanLabel(staleLabel); }); } + // Returns the date of the most recent commit on a pull request, or + // undefined when it cannot be determined. Used by ignore-bot-updates to + // make sure a human push is never mistaken for ignorable bot activity: + // pushes bump `updated_at` without leaving an issue event. + async getPullRequestLastCommitDate(issue) { + const issueLogger = new IssueLogger(issue); + try { + this._consumeIssueOperation(issue); + this.statistics?.incrementFetchedPullRequestsCount(); + const commits = await this.client.rest.pulls.listCommits({ + owner: github_context.repo.owner, + repo: github_context.repo.repo, + pull_number: issue.number, + per_page: 100 + }); + const lastCommit = commits.data[commits.data.length - 1]; + return lastCommit?.commit?.committer?.date; + } + catch (error) { + issueLogger.error(`Error when getting the last commit of this $$type: ${error.message}`); + } + } + // With ignore-bot-updates, an `updated_at` bump only counts as an update + // when something other than bot activity explains it: a non-bot issue + // event, or (for pull requests) a commit pushed since the stale marking. + // An unexplained bump (e.g. a body edit, which leaves no issue event) is + // treated as human activity so the action stays on the safe side. + async _hasHumanUpdateSince(issue, sinceDate, staleLabel, events) { + const issueLogger = new IssueLogger(issue); + const onlyIgnorableEvents = await this.hasOnlyStaleLabelingEventsSince(issue, sinceDate, staleLabel, events); + if (!onlyIgnorableEvents) { + return true; + } + if (issue.isPullRequest) { + const lastCommitDate = await this.getPullRequestLastCommitDate(issue); + if (lastCommitDate && + isDateMoreRecentThan(new Date(lastCommitDate), new Date(sinceDate), 15)) { + issueLogger.info(`$$type has a commit more recent than the stale marking: ${LoggerService.cyan(lastCommitDate)}`); + return true; + } + } + issueLogger.info(`Only bot or stale-labeling activity on $$type since: ${LoggerService.cyan(sinceDate)}`); + return false; + } async getPullRequest(issue) { const issueLogger = new IssueLogger(issue); try { @@ -52002,13 +52052,16 @@ class IssuesProcessor { // isDateMoreRecentThan makes sure they are not the same date within a certain tolerance (15 seconds in this case) let issueHasUpdateSinceStale = isDateMoreRecentThan(new Date(issue.updated_at), new Date(markedStaleOn), 15); // Check if the only update was the stale label being added + // (or, with ignore-bot-updates, any bot activity) if (issueHasUpdateSinceStale && shouldRemoveStaleWhenUpdated && !issue.markedStaleThisRun) { - const onlyStaleLabelAdded = await this.hasOnlyStaleLabelingEventsSince(issue, markedStaleOn, staleLabel, events); - if (onlyStaleLabelAdded) { + const hasHumanUpdate = this.options.ignoreBotUpdates + ? await this._hasHumanUpdateSince(issue, markedStaleOn, staleLabel, events) + : !(await this.hasOnlyStaleLabelingEventsSince(issue, markedStaleOn, staleLabel, events)); + if (!hasHumanUpdate) { issueHasUpdateSinceStale = false; - issueLogger.info(`Ignoring $$type update since only the stale label was added`); + issueLogger.info(`Ignoring $$type update since it was not caused by human activity`); } } issueLogger.info(`$$type has been updated since it was marked stale: ${LoggerService.cyan(issueHasUpdateSinceStale)}`); @@ -52028,7 +52081,13 @@ class IssuesProcessor { if (daysBeforeClose < 0) { return; // Nothing to do because we aren't closing stale issues } - const issueHasUpdateInCloseWindow = IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose); + // With ignore-bot-updates, bots bump `updated_at` constantly, so recency + // of `updated_at` proves nothing. The close window instead runs from the + // stale marking, blocked by any human update found above. + const issueHasUpdateInCloseWindow = this.options.ignoreBotUpdates + ? IssuesProcessor._updatedSince(markedStaleOn, daysBeforeClose) || + issueHasUpdateSinceStale + : IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose); issueLogger.info(`$$type has been updated in the last ${daysBeforeClose} days: ${LoggerService.cyan(issueHasUpdateInCloseWindow)}`); if (!issueHasCommentsSinceStale && !issueHasUpdateInCloseWindow) { issueLogger.info(`Closing $$type because it was last updated on: ${LoggerService.cyan(issue.updated_at)}`); @@ -106633,6 +106692,7 @@ function _getAndValidateArgs() { ignoreUpdates: getInput('ignore-updates') === 'true', ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'), ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), + ignoreBotUpdates: getInput('ignore-bot-updates') === 'true', exemptDraftPr: getInput('exempt-draft-pr') === 'true', closeIssueReason: getInput('close-issue-reason'), includeOnlyAssigned: getInput('include-only-assigned') === 'true', diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index 4043725c4..48256689e 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -62,6 +62,7 @@ describe('Issue', (): void => { labelsToRemoveWhenUnstale: '', labelsToAddWhenUnstale: '', ignoreUpdates: false, + ignoreBotUpdates: false, ignoreIssueUpdates: undefined, ignorePrUpdates: undefined, exemptDraftPr: false, diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index 62189bf16..a252838d5 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -650,7 +650,7 @@ export class IssuesProcessor { const issueLogger: IssueLogger = new IssueLogger(issue); issueLogger.info( - `Checking if only stale label added events on $$type since: ${LoggerService.cyan( + `Checking if only ignorable events on $$type since: ${LoggerService.cyan( sinceDate )}` ); @@ -674,6 +674,12 @@ export class IssuesProcessor { } return relevantEvents.every(event => { + // With ignore-bot-updates, any event caused by a bot account is + // ignorable — label syncs, automated milestoning, etc. + if (this.options.ignoreBotUpdates && event.actor?.type === 'Bot') { + return true; + } + if (event.event !== 'labeled') { return false; } @@ -682,6 +688,84 @@ export class IssuesProcessor { }); } + // Returns the date of the most recent commit on a pull request, or + // undefined when it cannot be determined. Used by ignore-bot-updates to + // make sure a human push is never mistaken for ignorable bot activity: + // pushes bump `updated_at` without leaving an issue event. + async getPullRequestLastCommitDate(issue: Issue): Promise { + const issueLogger: IssueLogger = new IssueLogger(issue); + + try { + this._consumeIssueOperation(issue); + this.statistics?.incrementFetchedPullRequestsCount(); + + const commits = await this.client.rest.pulls.listCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: issue.number, + per_page: 100 + }); + + const lastCommit = commits.data[commits.data.length - 1]; + + return lastCommit?.commit?.committer?.date; + } catch (error) { + issueLogger.error( + `Error when getting the last commit of this $$type: ${error.message}` + ); + } + } + + // With ignore-bot-updates, an `updated_at` bump only counts as an update + // when something other than bot activity explains it: a non-bot issue + // event, or (for pull requests) a commit pushed since the stale marking. + // An unexplained bump (e.g. a body edit, which leaves no issue event) is + // treated as human activity so the action stays on the safe side. + private async _hasHumanUpdateSince( + issue: Issue, + sinceDate: string, + staleLabel: string, + events: IIssueEvent[] + ): Promise { + const issueLogger: IssueLogger = new IssueLogger(issue); + + const onlyIgnorableEvents = await this.hasOnlyStaleLabelingEventsSince( + issue, + sinceDate, + staleLabel, + events + ); + + if (!onlyIgnorableEvents) { + return true; + } + + if (issue.isPullRequest) { + const lastCommitDate = await this.getPullRequestLastCommitDate(issue); + + if ( + lastCommitDate && + isDateMoreRecentThan(new Date(lastCommitDate), new Date(sinceDate), 15) + ) { + issueLogger.info( + `$$type has a commit more recent than the stale marking: ${LoggerService.cyan( + lastCommitDate + )}` + ); + + return true; + } + } + + issueLogger.info( + `Only bot or stale-labeling activity on $$type since: ${LoggerService.cyan( + sinceDate + )}` + ); + + return false; + } + async getPullRequest(issue: Issue): Promise { const issueLogger: IssueLogger = new IssueLogger(issue); @@ -797,22 +881,30 @@ export class IssuesProcessor { ); // Check if the only update was the stale label being added + // (or, with ignore-bot-updates, any bot activity) if ( issueHasUpdateSinceStale && shouldRemoveStaleWhenUpdated && !issue.markedStaleThisRun ) { - const onlyStaleLabelAdded = await this.hasOnlyStaleLabelingEventsSince( - issue, - markedStaleOn, - staleLabel, - events - ); - - if (onlyStaleLabelAdded) { + const hasHumanUpdate = this.options.ignoreBotUpdates + ? await this._hasHumanUpdateSince( + issue, + markedStaleOn, + staleLabel, + events + ) + : !(await this.hasOnlyStaleLabelingEventsSince( + issue, + markedStaleOn, + staleLabel, + events + )); + + if (!hasHumanUpdate) { issueHasUpdateSinceStale = false; issueLogger.info( - `Ignoring $$type update since only the stale label was added` + `Ignoring $$type update since it was not caused by human activity` ); } } @@ -852,10 +944,13 @@ export class IssuesProcessor { return; // Nothing to do because we aren't closing stale issues } - const issueHasUpdateInCloseWindow: boolean = IssuesProcessor._updatedSince( - issue.updated_at, - daysBeforeClose - ); + // With ignore-bot-updates, bots bump `updated_at` constantly, so recency + // of `updated_at` proves nothing. The close window instead runs from the + // stale marking, blocked by any human update found above. + const issueHasUpdateInCloseWindow: boolean = this.options.ignoreBotUpdates + ? IssuesProcessor._updatedSince(markedStaleOn, daysBeforeClose) || + issueHasUpdateSinceStale + : IssuesProcessor._updatedSince(issue.updated_at, daysBeforeClose); issueLogger.info( `$$type has been updated in the last ${daysBeforeClose} days: ${LoggerService.cyan( issueHasUpdateInCloseWindow diff --git a/src/enums/option.ts b/src/enums/option.ts index 3c1bb5158..8bad29c69 100644 --- a/src/enums/option.ts +++ b/src/enums/option.ts @@ -48,6 +48,7 @@ export enum Option { IgnoreUpdates = 'ignore-updates', IgnoreIssueUpdates = 'ignore-issue-updates', IgnorePrUpdates = 'ignore-pr-updates', + IgnoreBotUpdates = 'ignore-bot-updates', ExemptDraftPr = 'exempt-draft-pr', CloseIssueReason = 'close-issue-reason', OnlyIssueTypes = 'only-issue-types' diff --git a/src/interfaces/issue-event.ts b/src/interfaces/issue-event.ts index 0a2d43b64..59c777315 100644 --- a/src/interfaces/issue-event.ts +++ b/src/interfaces/issue-event.ts @@ -1,7 +1,9 @@ import {ILabel} from './label.js'; +import {IUser} from './user.js'; export interface IIssueEvent { created_at: string; event: string; label: ILabel; + actor?: IUser | null; } diff --git a/src/interfaces/issues-processor-options.ts b/src/interfaces/issues-processor-options.ts index 4ca6511e2..53de874f9 100644 --- a/src/interfaces/issues-processor-options.ts +++ b/src/interfaces/issues-processor-options.ts @@ -52,6 +52,7 @@ export interface IIssuesProcessorOptions { ignoreUpdates: boolean; ignoreIssueUpdates: boolean | undefined; ignorePrUpdates: boolean | undefined; + ignoreBotUpdates: boolean; exemptDraftPr: boolean; closeIssueReason: string; includeOnlyAssigned: boolean; diff --git a/src/main.ts b/src/main.ts index 228dbc916..ac647883f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -122,6 +122,7 @@ function _getAndValidateArgs(): IIssuesProcessorOptions { ignoreUpdates: core.getInput('ignore-updates') === 'true', ignoreIssueUpdates: _toOptionalBoolean('ignore-issue-updates'), ignorePrUpdates: _toOptionalBoolean('ignore-pr-updates'), + ignoreBotUpdates: core.getInput('ignore-bot-updates') === 'true', exemptDraftPr: core.getInput('exempt-draft-pr') === 'true', closeIssueReason: core.getInput('close-issue-reason'), includeOnlyAssigned: core.getInput('include-only-assigned') === 'true', From 1833f6439905c7711f5cb852d6352a02516520d4 Mon Sep 17 00:00:00 2001 From: Chris Nienhuis Date: Tue, 4 Aug 2026 12:31:50 -0500 Subject: [PATCH 2/2] style: apply prettier formatting --- src/classes/issues-processor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index a252838d5..851b37822 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -692,7 +692,9 @@ export class IssuesProcessor { // undefined when it cannot be determined. Used by ignore-bot-updates to // make sure a human push is never mistaken for ignorable bot activity: // pushes bump `updated_at` without leaving an issue event. - async getPullRequestLastCommitDate(issue: Issue): Promise { + async getPullRequestLastCommitDate( + issue: Issue + ): Promise { const issueLogger: IssueLogger = new IssueLogger(issue); try {