diff --git a/config/application/secret.properties.sample b/config/application/secret.properties.sample
index 99f4d517fdd6d498e991eae6bdd70c0255a4241c..47d466804ac9893d24b7b08c95d0e6ac47ae145d 100644
--- a/config/application/secret.properties.sample
+++ b/config/application/secret.properties.sample
@@ -16,4 +16,7 @@ eclipse.gitlab.access-token=
 
 ## Used to send mail through the EclipseFdn smtp connection
 quarkus.mailer.password=YOURGENERATEDAPPLICATIONPASSWORD
-quarkus.mailer.username=YOUREMAIL@gmail.com
\ No newline at end of file
+quarkus.mailer.username=YOUREMAIL@gmail.com
+
+## github app bot id (this would be used to identify bot's comments)
+eclipse.github.bot.id=s0m3ID
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index ec754369e5983b43b31117963d17f85ad035badf..b712261f63fcbd37bf876df5313fe140284ec448 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,5 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<!--
+ Copyright (c) 2020
+ 2025 Eclipse Foundation
+
+ This program and the accompanying materials are made
+ available under the terms of the Eclipse Public License 2.0
+ which is available at https://www.eclipse.org/legal/epl-2.0/
+
+ SPDX-License-Identifier: EPL-2.0
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.eclipsefoundation</groupId>
     <artifactId>git-eca</artifactId>
@@ -15,7 +28,7 @@
         <quarkus.platform.version>3.15.4</quarkus.platform.version>
         <surefire-plugin.version>3.3.1</surefire-plugin.version>
         <maven.compiler.parameters>true</maven.compiler.parameters>
-        <eclipse-api-version>1.2.3</eclipse-api-version>
+        <eclipse-api-version>1.2.5</eclipse-api-version>
         <auto-value.version>1.10.4</auto-value.version>
         <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
         <sonar.sources>src/main</sonar.sources>
@@ -75,8 +88,8 @@
             <artifactId>quarkus-smallrye-jwt</artifactId>
         </dependency>
         <dependency>
-          <groupId>io.quarkus</groupId>
-          <artifactId>quarkus-mailer</artifactId>
+            <groupId>io.quarkus</groupId>
+            <artifactId>quarkus-mailer</artifactId>
         </dependency>
 
         <!-- Annotation preprocessors - reduce all of the boiler plate -->
@@ -97,6 +110,12 @@
             <version>${org.mapstruct.version}</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>io.soabase.record-builder</groupId>
+            <artifactId>record-builder-processor</artifactId>
+            <version>45</version>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- Test requirements -->
         <dependency>
@@ -168,6 +187,11 @@
                             <artifactId>mapstruct-processor</artifactId>
                             <version>${org.mapstruct.version}</version>
                         </path>
+                        <path>
+                            <groupId>io.soabase.record-builder</groupId>
+                            <artifactId>record-builder-processor</artifactId>
+                            <version>45</version>
+                        </path>
                     </annotationProcessorPaths>
                 </configuration>
             </plugin>
@@ -184,4 +208,4 @@
             </plugin>
         </plugins>
     </build>
-</project>
+</project>
\ No newline at end of file
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java b/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java
index 17f7229b0e8a52112b4d748f74aa918f6b0f2c66..a03a00481c34b41c044d2185049bcf072ac95d25 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/GithubAPI.java
@@ -2,7 +2,7 @@
  * Copyright (c) 2022 Eclipse Foundation
  *
  * This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License 2.0
+ * available under the terms of the Eclipse Public License 2.0 
  * which is available at https://www.eclipse.org/legal/epl-2.0/
  *
  * Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
@@ -19,6 +19,8 @@ import org.eclipsefoundation.git.eca.api.models.GithubAccessToken;
 import org.eclipsefoundation.git.eca.api.models.GithubApplicationInstallationData;
 import org.eclipsefoundation.git.eca.api.models.GithubCommit;
 import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueComment;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentRequest;
 import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest;
 import org.jboss.resteasy.reactive.RestResponse;
 
@@ -29,6 +31,7 @@ import jakarta.ws.rs.POST;
 import jakarta.ws.rs.Path;
 import jakarta.ws.rs.PathParam;
 import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.Response;
 
@@ -59,7 +62,7 @@ public interface GithubAPI {
 
     /**
      * Retrieves a list of commits related to the given pull request.
-     * 
+     *
      * @param bearer authorization header value, access token for application with access to repo
      * @param apiVersion the version of the GH API to target when making the request
      * @param repo the repo name that is being targeted
@@ -74,7 +77,7 @@ public interface GithubAPI {
 
     /**
      * Posts an update to the Github API, using an access token to update a given pull requests commit status, targeted using the head sha.
-     * 
+     *
      * @param bearer authorization header value, access token for application with access to repo
      * @param apiVersion the version of the GH API to target when making the request
      * @param organization the organization that owns the targeted repo
@@ -89,6 +92,41 @@ public interface GithubAPI {
             @HeaderParam("X-GitHub-Api-Version") String apiVersion, @PathParam("org") String organization, @PathParam("repo") String repo,
             @PathParam("prHeadSha") String prHeadSha, GithubCommitStatusRequest commitStatusUpdate);
 
+    /**
+     * Adds a comment to a pull request using the issues API.
+     *
+     * @param bearer authorization header value, access token for application with access to repo
+     * @param apiVersion the version of the GH API to target when making the request
+     * @param organization the organization that owns the targeted repo
+     * @param repo the repo name that is being targeted
+     * @param issueNumber the number of the issue/PR to which the comment will be added.
+     * @param commentRequest the comment request containing the comment message
+     * @return response indicating the result of the operation.
+     */
+    @POST
+    @Path("repos/{org}/{repo}/issues/{issueNumber}/comments")
+    public Response addComment(@HeaderParam(HttpHeaders.AUTHORIZATION) String bearer,
+            @HeaderParam("X-GitHub-Api-Version") String apiVersion, @PathParam("org") String organization, @PathParam("repo") String repo,
+            @PathParam("issueNumber") int issueNumber, GithubIssueCommentRequest commentRequest);
+
+    /**
+     * Lists comments on an issue/pull request.
+     *
+     * @param bearer authorization header value, access token for application with access to repo
+     * @param apiVersion the version of the GH API to target when making the request
+     * @param organization the organization that owns the targeted repo
+     * @param repo the repo name that is being targeted
+     * @param issueNumber the number of the issue/PR
+     * @param perPage number of items to return per page, max 100
+     * @param page page number to return
+     * @return list of comments on the issue/pull request
+     */
+    @GET
+    @Path("repos/{org}/{repo}/issues/{issueNumber}/comments")
+    public RestResponse<List<GithubIssueComment>> getComments(@HeaderParam(HttpHeaders.AUTHORIZATION) String bearer,
+            @HeaderParam("X-GitHub-Api-Version") String apiVersion, @PathParam("org") String organization, @PathParam("repo") String repo,
+            @PathParam("issueNumber") int issueNumber, @QueryParam("per_page") int perPage, @QueryParam("page") int page);
+
     /**
      * Requires a JWT bearer token for the application to retrieve installations for. Returns a list of installations for the given
      * application.
@@ -117,7 +155,7 @@ public interface GithubAPI {
 
     /**
      * Returns a list of repositories for the given installation.
-     * 
+     *
      * @param params the general params for requests, including pagination
      * @param bearer JWT bearer token for the target installation
      * @return list of repositories for the installation as a response for pagination
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueComment.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueComment.java
new file mode 100644
index 0000000000000000000000000000000000000000..91a798a7d5334e243b284e19e8c45aa4647d58b9
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueComment.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2025 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.eclipsefoundation.git.eca.api.models;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+
+import io.soabase.recordbuilder.core.RecordBuilder;
+
+/**
+ * Model response for repos/{org}/{repo}/issues/{issueNumber}/comments
+ */
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@RecordBuilder
+public record GithubIssueComment(
+    String body,
+    Long id,
+    GithubUser user
+) {
+    @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+    @RecordBuilder
+    public static record GithubUser(
+        Long id
+    ) {}
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueCommentRequest.java b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueCommentRequest.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6ec978dd2a6016e5bcaba8e38eb609ce293a051
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/api/models/GithubIssueCommentRequest.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2025 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0 
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.git.eca.api.models;
+
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import io.soabase.recordbuilder.core.RecordBuilder;
+
+/**
+ * Model for creating a comment on a GitHub issue or pull request.
+ * Uses the issues API endpoint which works for both issues and PRs.
+ */
+@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
+@RecordBuilder
+public record GithubIssueCommentRequest(String body) {}
\ No newline at end of file
diff --git a/src/main/java/org/eclipsefoundation/git/eca/config/GithubBotConfig.java b/src/main/java/org/eclipsefoundation/git/eca/config/GithubBotConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..8d26a3e156d84bde21e4c9c6af21acf8807e69c2
--- /dev/null
+++ b/src/main/java/org/eclipsefoundation/git/eca/config/GithubBotConfig.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2025 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipsefoundation.git.eca.config;
+
+import io.smallrye.config.ConfigMapping;
+import java.util.Optional;
+
+/**
+ * The intention of this class is to provide an static bot ID, given that this app only supports one installation, this way we can avoid
+ * having to retrieve this information from the API on every start, or storing it in the DB.
+ */
+@ConfigMapping(prefix = "eclipse.github.bot")
+public interface GithubBotConfig {
+    Optional<Long> id();
+}
diff --git a/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java b/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java
index 803567edcab00ce75083e3d28e1bf6c4ab47613b..0f5e15a22ccc97236a72948008345a24ae8164b1 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/helper/GithubHelper.java
@@ -18,8 +18,11 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.StringUtils;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
@@ -30,8 +33,12 @@ import org.eclipsefoundation.git.eca.api.models.GithubApplicationInstallationDat
 import org.eclipsefoundation.git.eca.api.models.GithubCommit;
 import org.eclipsefoundation.git.eca.api.models.GithubCommit.ParentCommit;
 import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueComment;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentRequest;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentRequestBuilder;
 import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest;
 import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest;
+import org.eclipsefoundation.git.eca.config.GithubBotConfig;
 import org.eclipsefoundation.git.eca.config.WebhooksConfig;
 import org.eclipsefoundation.git.eca.dto.CommitValidationStatus;
 import org.eclipsefoundation.git.eca.dto.GithubApplicationInstallation;
@@ -53,9 +60,12 @@ import org.eclipsefoundation.persistence.model.RDBMSQuery;
 import org.eclipsefoundation.persistence.service.FilterService;
 import org.eclipsefoundation.utils.helper.DateTimeHelper;
 import org.eclipsefoundation.utils.helper.TransformationHelper;
+import org.jboss.resteasy.reactive.RestResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import io.quarkus.qute.Location;
+import io.quarkus.qute.Template;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
 import jakarta.ws.rs.BadRequestException;
@@ -78,6 +88,9 @@ public class GithubHelper {
     @ConfigProperty(name = "eclipse.github.default-api-version", defaultValue = "2022-11-28")
     String apiVersion;
 
+    @Inject
+    GithubBotConfig botConfig;
+
     @Inject
     WebhooksConfig webhooksConfig;
 
@@ -102,10 +115,13 @@ public class GithubHelper {
     @RestClient
     GithubAPI ghApi;
 
+    @Location("github/validation_failure.md")
+    Template failureMessage;
+
     /**
      * Using the wrapper and the passed unique information about a Github validation instance, lookup or create the tracking request and
      * validate the data.
-     * 
+     *
      * @param wrapper the wrapper for the current request
      * @param org the name of the GH organization
      * @param repoName the slug of the repo that has the PR to be validated
@@ -167,7 +183,7 @@ public class GithubHelper {
     /**
      * Generate a ValidationRequest object based on data pulled from Github, grabbing commits from the noted pull request using the
      * installation ID for access/authorization.
-     * 
+     *
      * @param installationId the ECA app installation ID for the organization
      * @param repositoryFullName the full name of the repository where the PR resides
      * @param pullRequestNumber the pull request number that is being validated
@@ -225,7 +241,7 @@ public class GithubHelper {
     /**
      * Process the current 'merge_group' request and create a commit status for the HEAD SHA. As commits need to pass branch rules including
      * the existing ECA check, we don't need to check the commits here.
-     * 
+     *
      * @param request information about the request from the GH webhook on what resource requested revalidation. Used to target the commit
      * status of the resources
      */
@@ -249,7 +265,7 @@ public class GithubHelper {
     /**
      * Process the current request and update the checks state to pending then success or failure. Contains verbose TRACE logging for more
      * info on the states of the validation for more information
-     * 
+     *
      * @param request information about the request from the GH webhook on what resource requested revalidation. Used to target the commit
      * status of the resources
      * @param vr the pseudo request generated from the contextual webhook data. Used to make use of existing validation logic.
@@ -275,14 +291,44 @@ public class GithubHelper {
         } else {
             LOGGER.trace(VALIDATION_LOGGING_MESSAGE, request.getRepository().getFullName(), pr.getNumber(), GithubCommitStatuses.FAILURE);
             updateCommitStatus(request, GithubCommitStatuses.FAILURE);
+            commentOnFailure(request,
+                    r
+                            .getCommits()
+                            .entrySet()
+                            .stream()
+                            .filter(e -> !e.getValue().getErrors().isEmpty())
+                            .map(e -> e.getKey())
+                            .map(hash -> findCommitByHash(vr, hash))
+                            .filter(Optional::isPresent)
+                            .map(Optional::get)
+                            .map(c -> c.getAuthor().getName())
+                            .collect(Collectors.toSet()),
+                    r
+                            .getCommits()
+                            .values()
+                            .stream()
+                            .flatMap(c -> c.getErrors().stream())
+                            .map(e -> e.getMessage())
+                            .collect(Collectors.toSet()));
         }
         return r;
     }
 
+    /**
+     * Searches for a commit in the validation request based on its hash value.
+     *
+     * @param vr The validation request containing the list of commits to search
+     * @param hash The hash value to search for
+     * @return An Optional containing the matching Commit if found, or empty if not found
+     */
+    private Optional<Commit> findCommitByHash(ValidationRequest vr, String hash) {
+        return vr.getCommits().stream().filter((commit) -> hash.equals(commit.getHash())).findFirst();
+    }
+
     /**
      * Shortcut method that will retrieve existing GH tracking info, create new entries if missing, and will update the state of existing
      * requests as well.
-     * 
+     *
      * @param installationId the installation ID for the ECA app in the given repository
      * @param repositoryFullName the full repository name for the target repo, e.g. eclipse/jetty
      * @param pr the pull request targeted by the validation request.
@@ -297,7 +343,7 @@ public class GithubHelper {
 
     /**
      * Checks if the Github tracking is present for the current request, and if missing will generate a new record and save it.
-     * 
+     *
      * @param tracking the optional tracking entry for the current request
      * @param request the pull request that is being validated
      * @param installationId the ECA app installation ID for the current request
@@ -343,7 +389,7 @@ public class GithubHelper {
 
     /**
      * Attempts to retrieve a webhook tracking record given the installation, repository, and pull request number.
-     * 
+     *
      * @param installationId the installation ID for the ECA app in the given repository
      * @param repositoryFullName the full repository name for the target repo, e.g. eclipse/jetty
      * @param pullRequestNumber the pull request number that is being processed currently
@@ -364,7 +410,7 @@ public class GithubHelper {
      * Using the configured GitHub application JWT and application ID, fetches all active installations and updates the DB cache containing
      * the installation data. After all records are updated, the starting timestamp is used to delete stale records that were updated before
      * the starting time.
-     * 
+     *
      * This does not use locking, but with the way that updates are done to look for long stale entries, multiple concurrent runs would not
      * lead to a loss of data/integrity.
      */
@@ -405,7 +451,7 @@ public class GithubHelper {
 
     /**
      * Simple helper method so we don't have to repeat static strings in multiple places.
-     * 
+     *
      * @param fullRepoName the full repo name including org for the target PR.
      * @return the full repo URL on Github for the request
      */
@@ -415,7 +461,7 @@ public class GithubHelper {
 
     /**
      * Sends off a POST to update the commit status given a context for the current PR.
-     * 
+     *
      * @param request the current webhook update request
      * @param state the state to set the status to
      * @param fingerprint the internal unique string for the set of commits being processed
@@ -427,9 +473,12 @@ public class GithubHelper {
             throw new IllegalStateException("Pull request should not be null when handling validation");
         }
 
-        LOGGER
-                .trace("Generated access token for installation {}: {}", request.getInstallation().getId(),
-                        jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken());
+        if (LOGGER.isTraceEnabled()) {
+            LOGGER
+                    .trace("Generated access token for installation {}: {}", request.getInstallation().getId(),
+                            jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken());
+        }
+
         ghApi
                 .updateStatus(jwtHelper.getGhBearerString(request.getInstallation().getId()), apiVersion,
                         request.getRepository().getOwner().getLogin(), request.getRepository().getName(), pr.getHead().getSha(),
@@ -443,9 +492,104 @@ public class GithubHelper {
                                 .build());
     }
 
+    /**
+     * Recursively fetches all comments from a pull request by handling pagination manually.
+     *
+     * @param bearer GitHub bearer token
+     * @param apiVersion GitHub API version
+     * @param org organization name
+     * @param repo repository name
+     * @param prNumber pull request number
+     * @return List of all comments from the pull request
+     */
+    private List<GithubIssueComment> getAllPullRequestCommentsByUserId(String bearer, String apiVersion, String org, String repo,
+            int prNumber, Long userId) {
+        int perPage = 100; // GitHub's maximum items per page
+        int page = 1; // Start from the first page
+        List<GithubIssueComment> allComments = new ArrayList<>();
+
+        // Given that there's no pagination in the response, we need to query until we get an empty response, that would mean that we've
+        // reached the end
+        while (page < 50) {
+            RestResponse<List<GithubIssueComment>> response = ghApi.getComments(bearer, apiVersion, org, repo, prNumber, perPage, page);
+
+            List<GithubIssueComment> comments = response.getEntity();
+            if (comments == null || comments.isEmpty()) {
+                break;
+            }
+
+            // We only want the comments made by the bot user
+            allComments.addAll(comments.stream().filter(comment -> comment.user().id() == userId).collect(Collectors.toList()));
+            page++;
+        }
+
+        return allComments;
+    }
+
+    /**
+     * This method posts a comment to the pull request detailing the validation errors and mentioning the usernames that need to take
+     * action.
+     *
+     * @param request The request containing repository and pull request information
+     * @param usernames Set of GitHub usernames that need to take action
+     * @param errors Set of error messages describing the validation failures
+     *
+     * @throws IllegalArgumentException if the request parameter is null
+     */
+    private void commentOnFailure(GithubWebhookRequest request, Set<String> usernames, Set<String> errors) {
+        // This should never happen given the logic behind the getPassed, but adding this just in case that logic changes
+        if (errors.isEmpty()) {
+            return;
+        }
+
+        String ghBearerString = jwtHelper.getGhBearerString(request.getInstallation().getId());
+        String login = request.getRepository().getOwner().getLogin();
+        String repositoryName = request.getRepository().getName();
+        Integer pullRequestNumber = request.getPullRequest().getNumber();
+
+        Set<String> nonMentionedUsers = Set.copyOf(usernames);
+
+        if (botConfig.id().isPresent()) {
+            // Get existing comments using pagination
+            List<GithubIssueComment> comments = getAllPullRequestCommentsByUserId(ghBearerString, apiVersion, login, repositoryName,
+                    pullRequestNumber, botConfig.id().get());
+
+            nonMentionedUsers = usernames
+                    .stream()
+                    .filter(username -> comments
+                            .stream()
+                            .noneMatch(
+                                    comment -> Objects.requireNonNullElse(comment.body(), "").contains(String.format("@%s", username))))
+                    .collect(Collectors.toSet());
+        }
+
+        // If all the users have already been mentioned, skip commenting
+        if (nonMentionedUsers.isEmpty()) {
+            LOGGER.debug("All users have already been mentioned in the comments, skipping comment creation.");
+            return;
+        }
+
+        GithubIssueCommentRequest comment = GithubIssueCommentRequestBuilder 
+                .builder()
+                .body(failureMessage.data("reasons", errors).data("usernames", nonMentionedUsers).render())
+                .build();
+
+        if (LOGGER.isTraceEnabled()) {
+            LOGGER
+                    .trace("Generated access token for installation {}: {}", request.getInstallation().getId(),
+                            jwtHelper.getGithubAccessToken(request.getInstallation().getId()).getToken());
+        }
+
+        LOGGER
+                .trace("Adding new comment to PR {} in repo {}/{}: {}", pullRequestNumber, TransformationHelper.formatLog(login),
+                        TransformationHelper.formatLog(repositoryName), comment.body());
+
+        ghApi.addComment(ghBearerString, apiVersion, login, repositoryName, pullRequestNumber, comment);
+    }
+
     /**
      * Wraps a nullable value fetch to handle errors and will return null if the value can't be retrieved.
-     * 
+     *
      * @param supplier the method with potentially nullable values
      * @return the value if it can be found, or null
      */
@@ -460,7 +604,7 @@ public class GithubHelper {
 
     /**
      * Validates required fields for processing requests.
-     * 
+     *
      * @param installationId the installation ID for the ECA app in the given repository
      * @param repositoryFullName the full repository name for the target repo, e.g. eclipse/jetty
      * @param pullRequestNumber the pull request number that is being processed currently
@@ -490,7 +634,7 @@ public class GithubHelper {
     /**
      * Converts the raw installation data from Github into a short record to be persisted to database as a form of persistent caching.
      * Checks database for existing record, and returns record with a touched date for existing entries.
-     * 
+     *
      * @param ghInstallation raw Github installation record for current application
      * @return the new or updated installation record to be persisted to the database.
      */
diff --git a/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java b/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java
index 95801ddf5a98deaf0d32f628383cd7696a124b2e..9d27dec43c186a76b4702eabe84262f88f2129d0 100644
--- a/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java
+++ b/src/main/java/org/eclipsefoundation/git/eca/model/ValidationResponse.java
@@ -1,14 +1,14 @@
 /*********************************************************************
-* Copyright (c) 2020 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
+ * Copyright (c) 2020 Eclipse Foundation.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0 
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Author: Martin Lowe <martin.lowe@eclipse-foundation.org>
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ **********************************************************************/
 package org.eclipsefoundation.git.eca.model;
 
 import java.time.ZonedDateTime;
@@ -26,7 +26,6 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonNaming;
 import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
 import com.google.auto.value.AutoValue;
-import com.google.auto.value.extension.memoized.Memoized;
 
 /**
  * Represents an internal response for a call to this API.
@@ -35,7 +34,7 @@ import com.google.auto.value.extension.memoized.Memoized;
  */
 @AutoValue
 @JsonNaming(LowerCamelCaseStrategy.class)
-@JsonDeserialize(builder = $AutoValue_ValidationResponse.Builder.class)
+@JsonDeserialize(builder = AutoValue_ValidationResponse.Builder.class)
 public abstract class ValidationResponse {
     public static final String NIL_HASH_PLACEHOLDER = "_nil";
 
@@ -54,7 +53,6 @@ public abstract class ValidationResponse {
         return getErrorCount() <= 0;
     }
 
-    @Memoized
     public int getErrorCount() {
         return getCommits().values().stream().mapToInt(s -> s.getErrors().size()).sum();
     }
diff --git a/src/main/resources/templates/github/validation_failure.md b/src/main/resources/templates/github/validation_failure.md
new file mode 100644
index 0000000000000000000000000000000000000000..67f9243a92d70f08b8e92dcaec6f332bdb1035aa
--- /dev/null
+++ b/src/main/resources/templates/github/validation_failure.md
@@ -0,0 +1,19 @@
+Hi{#for username in usernames} @{username}{/for} — thank you for your contribution!
+
+The [Eclipse Contributor Agreement (ECA)](https://www.eclipse.org/legal/eca/) check has failed for this pull request due to one of the following reasons:
+
+{#for reason in reasons}
+- {reason}
+{/for}
+
+To resolve this, please:
+
+1. Sign in or create an Eclipse Foundation account: https://accounts.eclipse.org/user/eca
+1. Ensure your GitHub username is linked to your Eclipse account
+1. Complete and submit the ECA form
+
+Once done, push a new commit (or rebase) to re-trigger the ECA validation.
+
+If you believe you've already completed these steps, please double-check your account settings or report an issue to [Eclipse Foundation Helpdesk](https://gitlab.eclipse.org/eclipsefdn/helpdesk).
+
+Thanks again for your contribution!
\ No newline at end of file
diff --git a/src/test/java/org/eclipsefoundation/git/eca/helper/GithubHelperTest.java b/src/test/java/org/eclipsefoundation/git/eca/helper/GithubHelperTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a8d3dd9aeeba8ac52a7782e455086876064f4e3
--- /dev/null
+++ b/src/test/java/org/eclipsefoundation/git/eca/helper/GithubHelperTest.java
@@ -0,0 +1,308 @@
+/**
+ * Copyright (c) 2025 Eclipse Foundation
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+
+package org.eclipsefoundation.git.eca.helper;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+import java.net.URI;
+import java.time.ZonedDateTime;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.microprofile.rest.client.inject.RestClient;
+import org.eclipsefoundation.git.eca.api.GithubAPI;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueComment;
+import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest;
+import org.eclipsefoundation.git.eca.model.Commit;
+import org.eclipsefoundation.git.eca.model.CommitStatus;
+import org.eclipsefoundation.git.eca.model.GitUser;
+import org.eclipsefoundation.git.eca.model.ValidationRequest;
+import org.eclipsefoundation.git.eca.model.ValidationResponse;
+import org.eclipsefoundation.git.eca.namespace.APIStatusCode;
+import org.eclipsefoundation.git.eca.namespace.ProviderType;
+import org.eclipsefoundation.git.eca.service.ValidationService;
+import org.eclipsefoundation.git.eca.test.api.MockGithubAPI;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import io.quarkus.test.InjectMock;
+import io.quarkus.test.junit.QuarkusTest;
+import jakarta.inject.Inject;
+
+@QuarkusTest
+class GithubHelperTest {
+
+    @Inject
+    GithubHelper helper;
+
+    @RestClient
+    @Inject
+    GithubAPI ghApi;
+
+    @InjectMock
+    ValidationService validationService;
+
+    /**
+     * Clean up the test environment before each test. This is important for Quarkus tests as beans maintain state between tests.
+     */
+    @BeforeEach
+    void setUp() {
+        // Clean up the MockGithubAPI state if it's our mock implementation
+        if (ghApi instanceof MockGithubAPI mockGithubApi) {
+            mockGithubApi.cleanState();
+        }
+
+        // Reset mock interactions and stubbing
+        Mockito.reset(validationService);
+    }
+
+    /**
+     * Test validation when multiple commits have ECA validation errors. Verifies that all users with invalid commits are properly notified.
+     */
+    @Test
+    void testHandleGithubWebhookValidation_MultipleCommitErrors() {
+        // Set up test data for the PR
+        String orgName = "test-org";
+        String repoName = "test-repo";
+        int prNumber = 42;
+
+        Commit testCommit1 = createTestCommit("abc123", "testUser", "test@example.com");
+        Commit testCommit2 = createTestCommit("def456", "testUser2", "test2@example.com");
+
+        // Create webhook request and validation request
+        GithubWebhookRequest request = createTestWebhookRequest("12345", repoName, orgName, prNumber, "abc123");
+        ValidationRequest vr = createValidationRequest(orgName, repoName, List.of(testCommit1, testCommit2));
+
+        // Create a mock validation response with multiple errors
+        ValidationResponse mockResponse = createErrorValidationResponse(
+                Map.of(testCommit1, List.of("Missing ECA for user"), testCommit2, List.of("Missing ECA for user", "Another error")));
+
+        // Mock validation service to return our error response
+        when(validationService.validateIncomingRequest(Mockito.any(), Mockito.any())).thenReturn(mockResponse);
+
+        // Execute the validation handler
+        helper.handleGithubWebhookValidation(request, vr, null);
+
+        // Verify that the correct error comment was added to the PR
+        verifyErrorComment(orgName, repoName, prNumber, List.of("testUser", "testUser2"), List.of("Missing ECA for user", "Another error"));
+    }
+
+    /**
+     * Test validation when new commits are added to a PR where some users were previously notified. Verifies that only new users are
+     * notified of validation errors.
+     */
+    @Test
+    void testHandleGithubWebhookValidation_OnlyNewUsersNotified() {
+        // Set up test data
+        String orgName = "test-org";
+        String repoName = "test-repo";
+        int prNumber = 42;
+
+        // First validation with initial commits
+        Commit testCommit1 = createTestCommit("abc123", "testUser", "test@example.com");
+        GithubWebhookRequest request = createTestWebhookRequest("12345", repoName, orgName, prNumber, "abc123");
+        ValidationRequest initialVr = createValidationRequest(orgName, repoName, List.of(testCommit1));
+
+        // Mock initial validation response
+        ValidationResponse initialMockResponse = createErrorValidationResponse(Map.of(testCommit1, List.of("Missing ECA for user")));
+        when(validationService.validateIncomingRequest(Mockito.any(), Mockito.any())).thenReturn(initialMockResponse);
+
+        // Execute initial validation
+        helper.handleGithubWebhookValidation(request, initialVr, null);
+
+        // Verify initial error comment
+        verifyErrorComment(orgName, repoName, prNumber, List.of("testUser"), List.of("Missing ECA for user"));
+
+        // Second validation with new commit
+        Commit testCommit3 = createTestCommit("ghi789", "testUser3", "test3@example.com");
+        ValidationRequest newVr = createValidationRequest(orgName, repoName, List.of(testCommit1, testCommit3));
+
+        // Mock new validation response
+        ValidationResponse newMockResponse = createErrorValidationResponse(
+                Map.of(testCommit1, List.of("Missing ECA for user"), testCommit3, List.of("Missing ECA for user", "Another error")));
+        when(validationService.validateIncomingRequest(Mockito.any(), Mockito.any())).thenReturn(newMockResponse);
+
+        // Execute new validation
+        helper.handleGithubWebhookValidation(request, newVr, null);
+
+        // Verify that only the new user is notified
+        verifyErrorComment(orgName, repoName, prNumber, List.of("testUser3"), List.of("Missing ECA for user", "Another error"));
+    }
+
+    @Test
+    void testHandleGithubWebhookValidation_NoErrors() {
+        // Set up test data for the PR
+        String orgName = "test-org";
+        String repoName = "test-repo";
+        int prNumber = 42;
+
+        Commit testCommit = createTestCommit("abc123", "testUser", "test@example.com");
+
+        // Create webhook request simulating a GitHub PR event
+        GithubWebhookRequest request = createTestWebhookRequest("12345", repoName, orgName, prNumber, "abc123");
+        ValidationRequest vr = createValidationRequest(orgName, repoName, List.of(testCommit));
+
+        // Create a mock successful validation response with no errors
+        ValidationResponse mockResponse = ValidationResponse
+                .builder()
+                .setStrictMode(false)
+                .setTrackedProject(true)
+                .setCommits(Map.of("abc123", CommitStatus.builder().build()))
+                .build();
+
+        // Configure mock to return our success response
+        when(validationService.validateIncomingRequest(Mockito.any(), Mockito.any())).thenReturn(mockResponse);
+
+        // Execute the validation handler
+        helper.handleGithubWebhookValidation(request, vr, null);
+
+        // Verify no comments were added
+        var comments = ghApi.getComments("", "", orgName, repoName, prNumber, 30, 1).getEntity();
+        assertTrue(comments.isEmpty(), "No comments should be added when there are no errors");
+    }
+
+    /**
+     * Creates a GitHub webhook request for testing purposes.
+     *
+     * @param installationId The installation ID
+     * @param repoName The repository name
+     * @param orgName The organization name
+     * @param prNumber The pull request number
+     * @param commitSha The commit SHA
+     * @return GithubWebhookRequest instance
+     */
+    private GithubWebhookRequest createTestWebhookRequest(String installationId, String repoName, String orgName, int prNumber,
+            String commitSha) {
+        return GithubWebhookRequest
+                .builder()
+                .setInstallation(GithubWebhookRequest.Installation.builder().setId(installationId).build())
+                .setRepository(GithubWebhookRequest.Repository
+                        .builder()
+                        .setName(repoName)
+                        .setFullName(orgName + "/" + repoName)
+                        .setOwner(GithubWebhookRequest.RepositoryOwner.builder().setLogin(orgName).build())
+                        .setHtmlUrl("https://github.com/" + orgName + "/" + repoName)
+                        .build())
+                .setPullRequest(GithubWebhookRequest.PullRequest
+                        .builder()
+                        .setNumber(prNumber)
+                        .setState("open")
+                        .setHead(GithubWebhookRequest.PullRequestHead.builder().setSha(commitSha).build())
+                        .build())
+                .build();
+    }
+
+    /**
+     * Creates a test commit with specified user details.
+     *
+     * @param commitHash The commit hash
+     * @param userName The user's name
+     * @param userEmail The user's email
+     * @return Commit instance
+     */
+    private Commit createTestCommit(String commitHash, String userName, String userEmail) {
+        GitUser testUser = GitUser.builder().setName(userName).setMail(userEmail).build();
+        return Commit
+                .builder()
+                .setHash(commitHash)
+                .setAuthor(testUser)
+                .setCommitter(testUser)
+                .setSubject("Test commit")
+                .setBody("Test commit body")
+                .setParents(Collections.emptyList())
+                .build();
+    }
+
+    /**
+     * Creates a validation response with an error status.
+     *
+     * @param commitHash The commit hash to associate the error with
+     * @param errorMessage The error message
+     * @return ValidationResponse instance
+     */
+    private ValidationResponse createErrorValidationResponse(Map<Commit, List<String>> commitErrors) {
+        Map<String, CommitStatus> commits = commitErrors.entrySet().stream().collect(HashMap::new, (map, entry) -> {
+            CommitStatus status = CommitStatus.builder().build();
+            entry.getValue().forEach(error -> status.addError(error, APIStatusCode.ERROR_AUTHOR));
+            map.put(entry.getKey().getHash(), status);
+        }, HashMap::putAll);
+
+        return ValidationResponse
+                .builder()
+                .setTime(ZonedDateTime.now())
+                .setCommits(commits)
+                .setTrackedProject(true)
+                .setStrictMode(true)
+                .build();
+    }
+
+    /**
+     * Creates a validation request for testing.
+     *
+     * @param orgName The organization name
+     * @param repoName The repository name
+     * @param commits The list of commits to validate
+     * @return ValidationRequest instance
+     */
+    private ValidationRequest createValidationRequest(String orgName, String repoName, List<Commit> commits) {
+        return ValidationRequest
+                .builder()
+                .setRepoUrl(URI.create("https://github.com/" + orgName + "/" + repoName))
+                .setProvider(ProviderType.GITHUB)
+                .setCommits(commits)
+                .build();
+    }
+
+    /**
+     * Verifies that an error comment was posted to GitHub with the expected content.
+     *
+     * @param orgName The organization name
+     * @param repoName The repository name
+     * @param prNumber The pull request number
+     * @param usernames List of GitHub usernames to be mentioned
+     * @param errors List of error messages
+     */
+    private void verifyErrorComment(String orgName, String repoName, int prNumber, List<String> usernames, List<String> errors) {
+        String mentions = usernames.stream().map(user -> "@" + user).collect(java.util.stream.Collectors.joining(" "));
+        String errorBullets = errors.stream().map(error -> "- " + error).collect(java.util.stream.Collectors.joining("\n"));
+
+        String expectedBody = String
+                .format("""
+                        Hi %s — thank you for your contribution!
+
+                        The [Eclipse Contributor Agreement (ECA)](https://www.eclipse.org/legal/eca/) check has failed for this pull request due to one of the following reasons:
+
+                        %s
+
+                        To resolve this, please:
+
+                        1. Sign in or create an Eclipse Foundation account: https://accounts.eclipse.org/user/eca
+                        1. Ensure your GitHub username is linked to your Eclipse account
+                        1. Complete and submit the ECA form
+
+                        Once done, push a new commit (or rebase) to re-trigger the ECA validation.
+
+                        If you believe you've already completed these steps, please double-check your account settings or report an issue to [Eclipse Foundation Helpdesk](https://gitlab.eclipse.org/eclipsefdn/helpdesk).
+
+                        Thanks again for your contribution!""",
+                        mentions, errorBullets);
+
+        // Get the comments and verify content
+        List<GithubIssueComment> comments = ghApi.getComments("", "", orgName, repoName, prNumber, 30, 1).getEntity();
+        assertEquals(1, comments.stream().filter(comment -> comment.body().equals(expectedBody)).toList().size(),
+                "Expected one comment with the expected body");
+    }
+
+}
diff --git a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java
index 7031f2d011f592ff731295737395743ff8749c9e..011d87550a63414aec5f3173016c85471e733826 100644
--- a/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java
+++ b/src/test/java/org/eclipsefoundation/git/eca/test/api/MockGithubAPI.java
@@ -1,26 +1,24 @@
 /*********************************************************************
-* Copyright (c) 2023 Eclipse Foundation.
-*
-* This program and the accompanying materials are made
-* available under the terms of the Eclipse Public License 2.0
-* which is available at https://www.eclipse.org/legal/epl-2.0/
-*
-* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
-*
-* SPDX-License-Identifier: EPL-2.0
-**********************************************************************/
+ * Copyright (c) 2023 Eclipse Foundation.
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org>
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ **********************************************************************/
 package org.eclipsefoundation.git.eca.test.api;
 
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.ws.rs.core.Response;
-
 import org.eclipse.microprofile.rest.client.inject.RestClient;
 import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters;
 import org.eclipsefoundation.git.eca.api.GithubAPI;
@@ -31,10 +29,18 @@ import org.eclipsefoundation.git.eca.api.models.GithubCommit.CommitData;
 import org.eclipsefoundation.git.eca.api.models.GithubCommit.GitCommitUser;
 import org.eclipsefoundation.git.eca.api.models.GithubCommit.GithubCommitUser;
 import org.eclipsefoundation.git.eca.api.models.GithubCommitStatusRequest;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueComment;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentBuilder;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentGithubUserBuilder;
+import org.eclipsefoundation.git.eca.api.models.GithubIssueCommentRequest;
 import org.eclipsefoundation.git.eca.api.models.GithubWebhookRequest.PullRequest;
+import org.eclipsefoundation.git.eca.config.GithubBotConfig;
 import org.jboss.resteasy.reactive.RestResponse;
 
 import io.quarkus.test.Mock;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.core.Response;
 
 @Mock
 @RestClient
@@ -43,10 +49,15 @@ public class MockGithubAPI implements GithubAPI {
 
     Map<String, Map<Integer, List<GithubCommit>>> commits;
     Map<String, Map<String, String>> commitStatuses;
+    Map<String, Map<Integer, List<GithubIssueComment>>> comments;
+
+    @Inject
+    GithubBotConfig githubBotConfig;
 
     public MockGithubAPI() {
         this.commitStatuses = new HashMap<>();
         this.commits = new HashMap<>();
+        this.comments = new HashMap<>();
         this.commits
                 .put("eclipsefdn/sample",
                         Map
@@ -110,4 +121,45 @@ public class MockGithubAPI implements GithubAPI {
     public Response getInstallationRepositories(BaseAPIParameters params, String bearer) {
         throw new UnsupportedOperationException("Unimplemented method 'getInstallationRepositories'");
     }
+
+    @Override
+    public RestResponse<List<GithubIssueComment>> getComments(String bearer, String apiVersion, String organization, String repo,
+            int pullNumber, int perPage, int page) {
+
+        // to avoid loops when loading all comments
+        if (page > 1) {
+            return RestResponse.ok(Collections.emptyList());
+        }
+
+        String repoFullName = organization + '/' + repo;
+        Map<Integer, List<GithubIssueComment>> repoMap = comments.getOrDefault(repoFullName, Collections.emptyMap());
+        List<GithubIssueComment> repoComments = repoMap.getOrDefault(pullNumber, Collections.emptyList());
+        return RestResponse.ok(repoComments);
+    }
+
+    @Override
+    public Response addComment(String bearer, String apiVersion, String organization, String repo, int issueNumber,
+            GithubIssueCommentRequest commentRequest) {
+        String repoFullName = organization + '/' + repo;
+        GithubIssueComment comment = GithubIssueCommentBuilder.builder()
+                .id(0L)
+                .body(commentRequest.body())
+                .user(GithubIssueCommentGithubUserBuilder.builder().id(githubBotConfig.id().orElse(0L)).build())
+                .build();
+
+        comments.computeIfAbsent(repoFullName, k -> new HashMap<>()).computeIfAbsent(issueNumber, k -> new ArrayList<>()).add(comment);
+
+        return Response.ok().build();
+    }
+
+    /**
+     * Cleans up the internal state of the mock.
+     * This method should be called before each test to ensure a clean state.
+     */
+    public void cleanState() {
+        this.commits = new HashMap<>();
+        this.commitStatuses = new HashMap<>();
+        this.comments = new HashMap<>();
+    }
+    
 }
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
index bd6e17d86aaa0dd58b868dcb16386a4bdc8f1ef4..4a6a40751bbd9f6c467eed308aa9428ec8c3d2b9 100644
--- a/src/test/resources/application.properties
+++ b/src/test/resources/application.properties
@@ -44,3 +44,6 @@ eclipse.gitlab.access-token=token_val
 
 ## Disable private project scan in test mode
 eclipse.scheduled.private-project.enabled=false
+
+## GitHub App bot ID
+eclipse.github.bot.id=111111
\ No newline at end of file