Repository: actions/stale (ts)

Issue:
Stale label not being removed after new activity
<!-- Have you tried the [debugging](https://github.com/actions/stale#debugging) section of the readme? -->

## Describe your issue

The `stale` label isn't being removed from PRs when there's new activity even though `remove-stale-when-updated` isn't set.

## Your stale action configuration

<!-- This is an example config, please copy/paste your config into it. -->

```yml
on:
  push:
    paths:
      - .github/workflows/triage-issues.yml
  schedule:
    # Once every day at midnight UTC
    - cron: "0 0 * * *"
  issue_comment:

jobs:
  stale:
    if: >
      startsWith(github.repository, 'Homebrew/') && (
        github.event_name != 'issue_comment' || (
          contains(github.event.issue.labels.*.name, 'stale') ||
          contains(github.event.pull_request.labels.*.name, 'stale')
        )
      )
    runs-on: ubuntu-latest
    steps:
      - name: Mark/Close Stale Issues and Pull Requests
        uses: actions/stale@v3
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          days-before-stale: 21
          days-before-close: 7
          stale-issue-message: >
            This issue has been automatically marked as stale because it has not had
            recent activity. It will be closed if no further activity occurs.
          stale-pr-message: >
            This pull request has been automatically marked as stale because it has not had
            recent activity. It will be closed if no further activity occurs.
          exempt-issue-labels: "gsoc-outreachy,help wanted,in progress"
          exempt-pr-labels: "gsoc-outreachy,help wanted,in progress"
```

## Further context

I made a comment on https://github.com/Homebrew/brew/pull/11140#issuecomment-835407365 that should have caused the action to remove the `stale` label. The action did run successfully (the logs are available [here](https://github.com/Homebrew/brew/runs/2535097842?check_suite_focus=true)). Here are the relevant lines from the logs:

```
[#11140] Found this pull request last updated 2021-05-08T15:44:35Z
[#11140] The option "onlyLabels" was not specified. Continuing the process for this pull request
[#11140] Days before pull request stale: 21
[#11140] The pull request is not closed nor locked. Trying to remove the close label...
[#11140] There is no close label on this pull request. Skip
[#11140] This pull request has a stale label
[#11140] This pull request has no assignee
[#11140] Skip the assignees checks
[#11140] Found a stale pull request
[#11140] Checking for label on pull request
[#11140] Pull request marked stale on: 2021-05-08T15:44:35Z
[#11140] Checking for comments on pull request since 2021-05-08T15:44:35Z
[#11140] Comments not made by actor or another bot: 0
[#11140] Pull request has been commented on: false
[#11140] Days before pull request close: 7
[#11140] Pull request has been updated: true
[#11140] Stale pull request is not old enough to close yet (hasComments? false, hasUpdate? true)
```

The line that says `Pull request marked stale on: 2021-05-08T15:44:35Z` seems to be incorrect, as the stale label was added 16 hours ago before that time. I wonder if the action is using the time of the latest comment as the time that the stale label was added.
`stale` label not removed from issue that had an update
## Describe your issue

`stale` label not removed from issue that had an update.

## Your stale action configuration

https://github.com/grpc/grpc-go/blob/master/.github/workflows/stale.yml

```yml
    steps:
    - uses: actions/stale@v3
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        days-before-stale: 6
        days-before-close: 7
        only-labels: 'Status: Requires Reporter Clarification'
        stale-issue-label: 'stale'
        stale-pr-label: 'stale'
        operations-per-run: 60
        stale-issue-message: ...
        stale-pr-message: ...
```

## Further context

Issue: https://github.com/grpc/grpc-go/issues/4522
Run: https://github.com/grpc/grpc-go/runs/2841504434?check_suite_focus=true#step:2:94

```
[#4522] Found this issue last updated at: 2021-06-16T15:04:02Z
[#4522] The option only-labels (​https://github.com/actions/stale#only-labels​) was specified to only process issues and pull requests with all those labels (1)
[#4522] ├── All the required labels are present on this issue
[#4522] └── Continuing the process for this issue
[#4522] Days before issue stale: 6
[#4522] The issue is not closed nor locked. Trying to remove the close label...
[#4522] There is no close label on this issue. Skip
[#4522] This issue has a stale label
[#4522] The option any-of-labels (​https://github.com/actions/stale#any-of-labels​) was not specified
[#4522] └── Continuing the process for this issue
[#4522] This issue has no milestone
[#4522] └── Skip the milestones checks
[#4522] The option exempt-all-assignees (​https://github.com/actions/stale#exempt-all-assignees​) is disabled. Only some specific assignees on this issue will skip the stale process
[#4522] ├── The option exempt-issue-assignees (​https://github.com/actions/stale#exempt-issue-assignees​) is disabled. No specific assignee can skip the stale process for this issue
[#4522] ├── The option exempt-assignees (​https://github.com/actions/stale#exempt-assignees​) is disabled. No specific assignee can skip the stale process for this issue
[#4522] ├── No assignee option was specified to skip the stale process for this issue
[#4522] └── Skip the assignees checks
[#4522] This issue is already stale
[#4522] Checking for label on this issue
[#4522] Issue marked stale on: 2021-06-16T04:52:42Z
[#4522] Checking for comments on issue since: 2021-06-16T04:52:42Z
[#4522] Comments not made by actor or another bot: 0
[#4522] Issue has been commented on: false
[#4522] Days before issue close: 7
[#4522] Issue has been updated: true
[#4522] Stale issue is not old enough to close yet (hasComments? false, hasUpdate? true)
[#4522] 1 operation consumed for this issue
```

Entries of note from the log above:
```
Found this issue last updated at: 2021-06-16T15:04:02Z
Issue marked stale on: 2021-06-16T04:52:42Z
Checking for comments on issue since: 2021-06-16T04:52:42Z
Comments not made by actor or another bot: 0
Issue has been commented on: false
```

Note that it sees my update at 15:04:02, after the marked stale time of 04:52:42.  But it says "Comments not made by actor or another bot: 0".  What does "actor" mean in this instance?
Don't filter comments by actor
## Describe your issue
Right now, stale will determine if an issue has been updated by checking for comments that were not made by a bot and comments that were not made by the `actor`. The "actor" is somewhat opaque and can sometimes mean "the person who initiated the run" or the "actions bot". Rather than rely on this, we should check for comments that are equal to the stale message. 
## Further context

<!-- If helpful please provide screenshots, logs, links to other related issues. -->


Pull request:
Filter comments by content instead of actor
<!-- List the change(s) you're making with this PR. -->

## Changes

Fixes #509 
Fixes #441 
Fixes #518

Patch:
diff --git a/dist/index.js b/dist/index.js
index 6646849..0142f70 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -291,7 +291,6 @@ class IssuesProcessor {
         return __awaiter(this, void 0, void 0, function* () {
             // get the next batch of issues
             const issues = yield this.getIssues(page);
-            const actor = yield this.getActor();
             if (issues.length <= 0) {
                 this._logger.info(logger_service_1.LoggerService.green(`No more issues found to process. Exiting...`));
                 (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.setOperationsCount(this.operations.getConsumedOperationsCount()).logStats();
@@ -309,7 +308,7 @@ class IssuesProcessor {
                 }
                 const issueLogger = new issue_logger_1.IssueLogger(issue);
                 yield issueLogger.grouping(`$$type #${issue.number}`, () => __awaiter(this, void 0, void 0, function* () {
-                    yield this.processIssue(issue, actor, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale);
+                    yield this.processIssue(issue, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale);
                 }));
             }
             if (!this.operations.hasRemainingOperations()) {
@@ -323,7 +322,7 @@ class IssuesProcessor {
             return this.processIssues(page + 1);
         });
     }
-    processIssue(issue, actor, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale) {
+    processIssue(issue, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale) {
         var _a;
         return __awaiter(this, void 0, void 0, function* () {
             (_a = this._statistics) === null || _a === void 0 ? void 0 : _a.incrementProcessedItemsCount(issue);
@@ -472,7 +471,7 @@ class IssuesProcessor {
             // Process the issue if it was marked stale
             if (issue.isStale) {
                 issueLogger.info(`This $$type is already stale`);
-                yield this._processStaleIssue(issue, staleLabel, actor, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, closeMessage, closeLabel);
+                yield this._processStaleIssue(issue, staleLabel, staleMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, closeMessage, closeLabel);
             }
             IssuesProcessor._endIssueProcessing(issue);
         });
@@ -499,20 +498,6 @@ class IssuesProcessor {
             }
         });
     }
-    // get the actor from the GitHub token or context
-    getActor() {
-        return __awaiter(this, void 0, void 0, function* () {
-            let actor;
-            try {
-                this.operations.consumeOperation();
-                actor = yield this.client.users.getAuthenticated();
-            }
-            catch (error) {
-                return github_1.context.actor;
-            }
-            return actor.data.login;
-        });
-    }
     // grab issues from github in batches of 100
     getIssues(page) {
         var _a;
@@ -564,12 +549,12 @@ class IssuesProcessor {
         });
     }
     // handle all of the stale issue logic when we find a stale issue
-    _processStaleIssue(issue, staleLabel, actor, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, closeMessage, closeLabel) {
+    _processStaleIssue(issue, staleLabel, staleMessage, labelsToAddWhenUnstale, labelsToRemoveWhenUnstale, closeMessage, closeLabel) {
         return __awaiter(this, void 0, void 0, function* () {
             const issueLogger = new issue_logger_1.IssueLogger(issue);
             const markedStaleOn = (yield this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
             issueLogger.info(`$$type marked stale on: ${logger_service_1.LoggerService.cyan(markedStaleOn)}`);
-            const issueHasComments = yield this._hasCommentsSince(issue, markedStaleOn, actor);
+            const issueHasComments = yield this._hasCommentsSince(issue, markedStaleOn, staleMessage);
             issueLogger.info(`$$type has been commented on: ${logger_service_1.LoggerService.cyan(issueHasComments)}`);
             const daysBeforeClose = issue.isPullRequest
                 ? this._getDaysBeforePrClose()
@@ -614,7 +599,7 @@ class IssuesProcessor {
         });
     }
     // checks to see if a given issue is still stale (has had activity on it)
-    _hasCommentsSince(issue, sinceDate, actor) {
+    _hasCommentsSince(issue, sinceDate, staleMessage) {
         return __awaiter(this, void 0, void 0, function* () {
             const issueLogger = new issue_logger_1.IssueLogger(issue);
             issueLogger.info(`Checking for comments on $$type since: ${logger_service_1.LoggerService.cyan(sinceDate)}`);
@@ -623,8 +608,8 @@ class IssuesProcessor {
             }
             // find any comments since the date
             const comments = yield this.listIssueComments(issue.number, sinceDate);
-            const filteredComments = comments.filter(comment => comment.user.type === 'User' && comment.user.login !== actor);
-            issueLogger.info(`Comments not made by actor or another bot: ${logger_service_1.LoggerService.cyan(filteredComments.length)}`);
+            const filteredComments = comments.filter(comment => comment.user.type === 'User' && comment.body.toLowerCase() !== staleMessage.toLowerCase());
+            issueLogger.info(`Comments that are not the stale comment or another bot: ${logger_service_1.LoggerService.cyan(filteredComments.length)}`);
             // if there are any user comments returned
             return filteredComments.length > 0;
         });
@@ -5792,14 +5777,17 @@ ansiEscapes.iTerm = {
 /***/ }),
 
 /***/ 2068:
-/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
+/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __nccwpck_require__) => {
 
 "use strict";
-/* module decorator */ module = __nccwpck_require__.nmd(module);
-
-
+__nccwpck_require__.r(__webpack_exports__);
+/* harmony export */ __nccwpck_require__.d(__webpack_exports__, {
+/* harmony export */   "default": () => __WEBPACK_DEFAULT_EXPORT__
+/* harmony export */ });
 const ANSI_BACKGROUND_OFFSET = 10;
 
+const wrapAnsi16 = (offset = 0) => code => `\u001B[${code + offset}m`;
+
 const wrapAnsi256 = (offset = 0) => code => `\u001B[${38 + offset};5;${code}m`;
 
 const wrapAnsi16m = (offset = 0) => (red, green, blue) => `\u001B[${38 + offset};2;${red};${green};${blue}m`;
@@ -5893,8 +5881,10 @@ function assembleStyles() {
 	styles.color.close = '\u001B[39m';
 	styles.bgColor.close = '\u001B[49m';
 
+	styles.color.ansi = wrapAnsi16();
 	styles.color.ansi256 = wrapAnsi256();
 	styles.color.ansi16m = wrapAnsi16m();
+	styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
 	styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
 	styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
 
@@ -5949,17 +5939,67 @@ function assembleStyles() {
 		hexToAnsi256: {
 			value: hex => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
 			enumerable: false
+		},
+		ansi256ToAnsi: {
+			value: code => {
+				if (code < 8) {
+					return 30 + code;
+				}
+
+				if (code < 16) {
+					return 90 + (code - 8);
+				}
+
+				let red;
+				let green;
+				let blue;
+
+				if (code >= 232) {
+					red = (((code - 232) * 10) + 8) / 255;
+					green = red;
+					blue = red;
+				} else {
+					code -= 16;
+
+					const remainder = code % 36;
+
+					red = Math.floor(code / 36) / 5;
+					green = Math.floor(remainder / 6) / 5;
+					blue = (remainder % 6) / 5;
+				}
+
+				const value = Math.max(red, green, blue) * 2;
+
+				if (value === 0) {
+					return 30;
+				}
+
+				let result = 30 + ((Math.round(blue) << 2) | (Math.round(green) << 1) | Math.round(red));
+
+				if (value === 2) {
+					result += 60;
+				}
+
+				return result;
+			},
+			enumerable: false
+		},
+		rgbToAnsi: {
+			value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
+			enumerable: false
+		},
+		hexToAnsi: {
+			value: hex => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
+			enumerable: false
 		}
 	});
 
 	return styles;
 }
 
-// Make the export immutable
-Object.defineProperty(module, 'exports', {
-	enumerable: true,
-	get: assembleStyles
-});
+const ansiStyles = assembleStyles();
+
+/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (ansiStyles);
 
 
 /***/ }),
@@ -8950,8 +8990,8 @@ module.exports = require("zlib");;
 /******/ 		}
 /******/ 		// Create a new module (and put it into the cache)
 /******/ 		var module = __webpack_module_cache__[moduleId] = {
-/******/ 			id: moduleId,
-/******/ 			loaded: false,
+/******/ 			// no module.id needed
+/******/ 			// no module.loaded needed
 /******/ 			exports: {}
 /******/ 		};
 /******/ 	
@@ -8964,20 +9004,36 @@ module.exports = require("zlib");;
 /******/ 			if(threw) delete __webpack_module_cache__[moduleId];
 /******/ 		}
 /******/ 	
-/******/ 		// Flag the module as loaded
-/******/ 		module.loaded = true;
-/******/ 	
 /******/ 		// Return the exports of the module
 /******/ 		return module.exports;
 /******/ 	}
 /******/ 	
 /************************************************************************/
-/******/ 	/* webpack/runtime/node module decorator */
+/******/ 	/* webpack/runtime/define property getters */
+/******/ 	(() => {
+/******/ 		// define getter functions for harmony exports
+/******/ 		__nccwpck_require__.d = (exports, definition) => {
+/******/ 			for(var key in definition) {
+/******/ 				if(__nccwpck_require__.o(definition, key) && !__nccwpck_require__.o(exports, key)) {
+/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
+/******/ 				}
+/******/ 			}
+/******/ 		};
+/******/ 	})();
+/******/ 	
+/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
+/******/ 	(() => {
+/******/ 		__nccwpck_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop)
+/******/ 	})();
+/******/ 	
+/******/ 	/* webpack/runtime/make namespace object */
 /******/ 	(() => {
-/******/ 		__nccwpck_require__.nmd = (module) => {
-/******/ 			module.paths = [];
-/******/ 			if (!module.children) module.children = [];
-/******/ 			return module;
+/******/ 		// define __esModule on exports
+/******/ 		__nccwpck_require__.r = (exports) => {
+/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
+/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
+/******/ 			}
+/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
 /******/ 		};
 /******/ 	})();
 /******/ 	
diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts
index 6acc00d..b28649f 100644
--- a/src/classes/issues-processor.ts
+++ b/src/classes/issues-processor.ts
@@ -105,7 +105,6 @@ export class IssuesProcessor {
   async processIssues(page: Readonly<number> = 1): Promise<number> {
     // get the next batch of issues
     const issues: Issue[] = await this.getIssues(page);
-    const actor: string = await this.getActor();
 
     if (issues.length <= 0) {
       this._logger.info(
@@ -145,7 +144,6 @@ export class IssuesProcessor {
       await issueLogger.grouping(`$$type #${issue.number}`, async () => {
         await this.processIssue(
           issue,
-          actor,
           labelsToAddWhenUnstale,
           labelsToRemoveWhenUnstale
         );
@@ -184,7 +182,6 @@ export class IssuesProcessor {
 
   async processIssue(
     issue: Issue,
-    actor: string,
     labelsToAddWhenUnstale: Readonly<string>[],
     labelsToRemoveWhenUnstale: Readonly<string>[]
   ): Promise<void> {
@@ -455,7 +452,7 @@ export class IssuesProcessor {
       await this._processStaleIssue(
         issue,
         staleLabel,
-        actor,
+        staleMessage,
         labelsToAddWhenUnstale,
         labelsToRemoveWhenUnstale,
         closeMessage,
@@ -488,20 +485,6 @@ export class IssuesProcessor {
     }
   }
 
-  // get the actor from the GitHub token or context
-  async getActor(): Promise<string> {
-    let actor;
-
-    try {
-      this.operations.consumeOperation();
-      actor = await this.client.users.getAuthenticated();
-    } catch (error) {
-      return context.actor;
-    }
-
-    return actor.data.login;
-  }
-
   // grab issues from github in batches of 100
   async getIssues(page: number): Promise<Issue[]> {
     // generate type for response
@@ -568,7 +551,7 @@ export class IssuesProcessor {
   private async _processStaleIssue(
     issue: Issue,
     staleLabel: string,
-    actor: string,
+    staleMessage: string,
     labelsToAddWhenUnstale: Readonly<string>[],
     labelsToRemoveWhenUnstale: Readonly<string>[],
     closeMessage?: string,
@@ -584,7 +567,7 @@ export class IssuesProcessor {
     const issueHasComments: boolean = await this._hasCommentsSince(
       issue,
       markedStaleOn,
-      actor
+      staleMessage
     );
     issueLogger.info(
       `$$type has been commented on: ${LoggerService.cyan(issueHasComments)}`
@@ -672,7 +655,7 @@ export class IssuesProcessor {
   private async _hasCommentsSince(
     issue: Issue,
     sinceDate: string,
-    actor: string
+    staleMessage: string
   ): Promise<boolean> {
     const issueLogger: IssueLogger = new IssueLogger(issue);
 
@@ -688,11 +671,13 @@ export class IssuesProcessor {
     const comments = await this.listIssueComments(issue.number, sinceDate);
 
     const filteredComments = comments.filter(
-      comment => comment.user.type === 'User' && comment.user.login !== actor
+      comment =>
+        comment.user.type === 'User' &&
+        comment.body.toLowerCase() !== staleMessage.toLowerCase()
     );
 
     issueLogger.info(
-      `Comments not made by actor or another bot: ${LoggerService.cyan(
+      `Comments that are not the stale comment or another bot: ${LoggerService.cyan(
         filteredComments.length
       )}`
     );
diff --git a/src/interfaces/comment.ts b/src/interfaces/comment.ts
index 33196f6..f6f9634 100644
--- a/src/interfaces/comment.ts
+++ b/src/interfaces/comment.ts
@@ -2,4 +2,5 @@ import {IUser} from './user';
 
 export interface IComment {
   user: IUser;
+  body: string;
 }


Test patch:
diff --git a/__tests__/any-of-labels.spec.ts b/__tests__/any-of-labels.spec.ts
index 6aa1560..1b96ccf 100644
--- a/__tests__/any-of-labels.spec.ts
+++ b/__tests__/any-of-labels.spec.ts
@@ -1139,7 +1139,6 @@ class IssuesProcessorBuilder {
   build(): IssuesProcessorMock {
     return new IssuesProcessorMock(
       this._options,
-      async () => 'abot',
       async p => (p === 1 ? this._issues : []),
       async () => [],
       async () => new Date().toDateString()
diff --git a/__tests__/assignees.spec.ts b/__tests__/assignees.spec.ts
index 073ce16..86c4534 100644
--- a/__tests__/assignees.spec.ts
+++ b/__tests__/assignees.spec.ts
@@ -48,7 +48,6 @@ describe('assignees options', (): void => {
   const setProcessor = () => {
     processor = new IssuesProcessorMock(
       opts,
-      async () => 'abot',
       async p => (p === 1 ? testIssueList : []),
       async () => [],
       async () => new Date().toDateString()
diff --git a/__tests__/classes/issues-processor-mock.ts b/__tests__/classes/issues-processor-mock.ts
index 730235d..c1b2100 100644
--- a/__tests__/classes/issues-processor-mock.ts
+++ b/__tests__/classes/issues-processor-mock.ts
@@ -6,7 +6,6 @@ import {IIssuesProcessorOptions} from '../../src/interfaces/issues-processor-opt
 export class IssuesProcessorMock extends IssuesProcessor {
   constructor(
     options: IIssuesProcessorOptions,
-    getActor?: () => Promise<string>,
     getIssues?: (page: number) => Promise<Issue[]>,
     listIssueComments?: (
       issueNumber: number,
@@ -19,10 +18,6 @@ export class IssuesProcessorMock extends IssuesProcessor {
   ) {
     super(options);
 
-    if (getActor) {
-      this.getActor = getActor;
-    }
-
     if (getIssues) {
       this.getIssues = getIssues;
     }
diff --git a/__tests__/main.spec.ts b/__tests__/main.spec.ts
index 303ca95..f250fca 100644
--- a/__tests__/main.spec.ts
+++ b/__tests__/main.spec.ts
@@ -16,7 +16,6 @@ test('processing an issue with no label will make it stale and close it, if it i
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -48,7 +47,6 @@ test('processing an issue with no label and a start date as ECMAScript epoch in
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -80,7 +78,6 @@ test('processing an issue with no label and a start date as ECMAScript epoch in
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -112,7 +109,6 @@ test('processing an issue with no label and a start date as ECMAScript epoch in
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -144,7 +140,6 @@ test('processing an issue with no label and a start date as ECMAScript epoch in
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -176,7 +171,6 @@ test('processing an issue with no label and a start date as ISO 8601 being befor
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -208,7 +202,6 @@ test('processing an issue with no label and a start date as ISO 8601 being after
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -240,7 +233,6 @@ test('processing an issue with no label and a start date as RFC 2822 being befor
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -272,7 +264,6 @@ test('processing an issue with no label and a start date as RFC 2822 being after
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -296,7 +287,6 @@ test('processing an issue with no label will make it stale and close it, if it i
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -321,7 +311,6 @@ test('processing an issue with no label will make it stale and not close it, if
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -344,7 +333,6 @@ test('processing an issue with no label will make it stale and not close it if d
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -368,7 +356,6 @@ test('processing an issue with no label will make it stale and not close it if d
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -392,7 +379,6 @@ test('processing an issue with no label will not make it stale if days-before-st
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -417,7 +403,6 @@ test('processing an issue with no label will not make it stale if days-before-st
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -445,7 +430,6 @@ test('processing an issue with no label will make it stale but not close it', as
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -476,7 +460,6 @@ test('processing a stale issue will close it', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -507,7 +490,6 @@ test('processing a stale issue containing a space in the label will close it', a
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -538,7 +520,6 @@ test('processing a stale issue containing a slash in the label will close it', a
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -570,7 +551,6 @@ test('processing a stale issue will close it when days-before-issue-stale overri
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -601,7 +581,6 @@ test('processing a stale PR will close it', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -633,7 +612,6 @@ test('processing a stale PR will close it when days-before-pr-stale override day
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -665,7 +643,6 @@ test('processing a stale issue will close it even if configured not to mark as s
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -698,7 +675,6 @@ test('processing a stale issue will close it even if configured not to mark as s
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -730,7 +706,6 @@ test('processing a stale PR will close it even if configured not to mark as stal
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -763,7 +738,6 @@ test('processing a stale PR will close it even if configured not to mark as stal
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -791,7 +765,6 @@ test('closed issues will not be marked stale', async () => {
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => []
   );
@@ -818,7 +791,6 @@ test('stale closed issues will not be closed', async () => {
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -846,7 +818,6 @@ test('closed prs will not be marked stale', async () => {
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -874,7 +845,6 @@ test('stale closed prs will not be closed', async () => {
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -901,10 +871,8 @@ test('locked issues will not be marked stale', async () => {
       true
     )
   ];
-  const processor = new IssuesProcessorMock(
-    DefaultProcessorOptions,
-    async () => 'abot',
-    async p => (p === 1 ? TestIssueList : [])
+  const processor = new IssuesProcessorMock(DefaultProcessorOptions, async p =>
+    p === 1 ? TestIssueList : []
   );
 
   // process our fake issue list
@@ -930,7 +898,6 @@ test('stale locked issues will not be closed', async () => {
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -957,10 +924,8 @@ test('locked prs will not be marked stale', async () => {
       true
     )
   ];
-  const processor = new IssuesProcessorMock(
-    DefaultProcessorOptions,
-    async () => 'abot',
-    async p => (p === 1 ? TestIssueList : [])
+  const processor = new IssuesProcessorMock(DefaultProcessorOptions, async p =>
+    p === 1 ? TestIssueList : []
   );
 
   // process our fake issue list
@@ -986,7 +951,6 @@ test('stale locked prs will not be closed', async () => {
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1016,7 +980,6 @@ test('exempt issue labels will not be marked stale', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1046,7 +1009,6 @@ test('exempt issue labels will not be marked stale (multi issue label with space
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1075,7 +1037,6 @@ test('exempt issue labels will not be marked stale (multi issue label)', async (
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1122,7 +1083,6 @@ test('exempt pr labels will not be marked stale', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1151,14 +1111,14 @@ test('exempt issue labels will not be marked stale and will remove the existing
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [
       {
         user: {
           login: 'notme',
           type: 'User'
-        }
+        },
+        body: 'Body'
       }
     ], // return a fake comment to indicate there was an update
     async () => new Date().toDateString()
@@ -1206,7 +1166,6 @@ test('stale issues should not be closed if days is set to -1', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1234,14 +1193,14 @@ test('stale label should be removed if a comment was added to a stale issue', as
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [
       {
         user: {
           login: 'notme',
           type: 'User'
-        }
+        },
+        body: 'Body'
       }
     ], // return a fake comment to indicate there was an update
     async () => new Date().toDateString()
@@ -1275,14 +1234,14 @@ test('when the option "labelsToAddWhenUnstale" is set, the labels should be adde
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [
       {
         user: {
           login: 'notme',
           type: 'User'
-        }
+        },
+        body: 'Body'
       }
     ], // return a fake comment to indicate there was an update
     async () => new Date().toDateString()
@@ -1315,14 +1274,14 @@ test('stale label should not be removed if a comment was added by the bot (and t
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [
       {
         user: {
           login: 'abot',
           type: 'User'
-        }
+        },
+        body: 'This issue is stale'
       }
     ], // return a fake comment to indicate there was an update by the bot
     async () => new Date().toDateString()
@@ -1355,9 +1314,8 @@ test('stale label containing a space should be removed if a comment was added to
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
-    async () => [{user: {login: 'notme', type: 'User'}}], // return a fake comment to indicate there was an update
+    async () => [{user: {login: 'notme', type: 'User'}, body: 'Body'}], // return a fake comment to indicate there was an update
     async () => new Date().toDateString()
   );
 
@@ -1387,7 +1345,6 @@ test('stale issues should not be closed until after the closed number of days',
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1420,7 +1377,6 @@ test('stale issues should be closed if the closed nubmer of days (additive) is a
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1452,7 +1408,6 @@ test('stale issues should not be closed until after the closed number of days (l
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1485,7 +1440,6 @@ test('skips stale message on issues when stale-issue-message is empty', async ()
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1530,7 +1484,6 @@ test('send stale message on issues when stale-issue-message is not empty', async
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1575,7 +1528,6 @@ test('skips stale message on prs when stale-pr-message is empty', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1620,7 +1572,6 @@ test('send stale message on prs when stale-pr-message is not empty', async () =>
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1662,7 +1613,6 @@ test('git branch is deleted when option is enabled', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1692,7 +1642,6 @@ test('git branch is not deleted when issue is not pull request', async () => {
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1724,7 +1673,6 @@ test('an issue without a milestone will be marked as stale', async () => {
   ];
   const processor = new IssuesProcessorMock(
     DefaultProcessorOptions,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1758,7 +1706,6 @@ test('an issue without an exempted milestone will be marked as stale', async ()
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1792,7 +1739,6 @@ test('an issue with an exempted milestone will not be marked as stale', async ()
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1826,7 +1772,6 @@ test('an issue with an exempted milestone will not be marked as stale (multi mil
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1860,7 +1805,6 @@ test('an issue with an exempted milestone will not be marked as stale (multi mil
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1895,7 +1839,6 @@ test('an issue with an exempted milestone but without an exempted issue mileston
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1930,7 +1873,6 @@ test('an issue with an exempted milestone but with another exempted issue milest
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1965,7 +1907,6 @@ test('an issue with an exempted milestone and with an exempted issue milestone w
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -1993,7 +1934,6 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2020,7 +1960,6 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2047,7 +1986,6 @@ test('processing an issue opened since 2 days and with the option "daysBeforeIss
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2081,7 +2019,6 @@ test('processing a pull request opened since 2 days and with the option "daysBef
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2115,7 +2052,6 @@ test('processing a pull request opened since 2 days and with the option "daysBef
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2149,7 +2085,6 @@ test('processing a pull request opened since 2 days and with the option "daysBef
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2186,7 +2121,6 @@ test('processing a previously closed issue with a close label will remove the cl
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2222,7 +2156,6 @@ test('processing a closed issue with a close label will not remove the close lab
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2258,7 +2191,6 @@ test('processing a locked issue with a close label will not remove the close lab
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async () => [],
     async () => new Date().toDateString()
@@ -2298,7 +2230,6 @@ test('processing an issue stale since less than the daysBeforeStale with a stale
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async (): Promise<IComment[]> => Promise.resolve([]),
     async () => labelCreatedAt.toDateString()
@@ -2339,7 +2270,6 @@ test('processing an issue stale since less than the daysBeforeStale without a st
   ];
   const processor = new IssuesProcessorMock(
     opts,
-    async () => 'abot',
     async p => (p === 1 ? TestIssueList : []),
     async (): Promise<IComment[]> => Promise.resolve([]),
     async () => new Date().toDateString()
diff --git a/__tests__/milestones.spec.ts b/__tests__/milestones.spec.ts
index 5f2b7ed..3483d59 100644
--- a/__tests__/milestones.spec.ts
+++ b/__tests__/milestones.spec.ts
@@ -39,7 +39,6 @@ describe('milestones options', (): void => {
   const setProcessor = () => {
     processor = new IssuesProcessorMock(
       opts,
-      async () => 'abot',
       async p => (p === 1 ? testIssueList : []),
       async () => [],
       async () => new Date().toDateString()
diff --git a/__tests__/only-labels.spec.ts b/__tests__/only-labels.spec.ts
index 042642b..80b17a5 100644
--- a/__tests__/only-labels.spec.ts
+++ b/__tests__/only-labels.spec.ts
@@ -1139,7 +1139,6 @@ class IssuesProcessorBuilder {
   build(): IssuesProcessorMock {
     return new IssuesProcessorMock(
       this._options,
-      async () => 'abot',
       async p => (p === 1 ? this._issues : []),
       async () => [],
       async () => new Date().toDateString()
diff --git a/__tests__/operations-per-run.spec.ts b/__tests__/operations-per-run.spec.ts
index e73593c..7d84f5f 100644
--- a/__tests__/operations-per-run.spec.ts
+++ b/__tests__/operations-per-run.spec.ts
@@ -205,7 +205,6 @@ class SUT {
   private async _setProcessor(): Promise<number> {
     this.processor = new IssuesProcessorMock(
       this._opts,
-      async () => 'abot',
       async p => (p === 1 ? this._testIssueList : []),
       async () => [],
       async () => new Date().toDateString()
diff --git a/__tests__/remove-stale-when-updated.spec.ts b/__tests__/remove-stale-when-updated.spec.ts
index 91b5bef..79ff5e6 100644
--- a/__tests__/remove-stale-when-updated.spec.ts
+++ b/__tests__/remove-stale-when-updated.spec.ts
@@ -539,14 +539,14 @@ class IssuesProcessorBuilder {
   build(): IssuesProcessorMock {
     return new IssuesProcessorMock(
       this._options,
-      async () => 'abot',
       async p => (p === 1 ? this._issues : []),
       async () => [
         {
           user: {
             login: 'notme',
             type: 'User'
-          }
+          },
+          body: 'body'
         }
       ],
       async () => new Date().toDateString()