The GitHub REST API for issues exposes an endpoint to create comments. Because pull requests are implemented as issues, the same endpoint can also be used to comment on pull requests. As the documentation states, “every pull request is an issue, but not every issue is a pull request.”
Permissions required to write comments in an issue
To create a comment, the token used by your workflow must have write access to issues and pull requests. Since the focus of this article is automation, we’ll use GitHub App installation access tokens as the input to the action.
Working with the REST API endpoints for issue comments
In the context of GitHub Actions, one of the simplest ways to interact with the GitHub API is by using the actions/github-script action. It allows you to run an asynchronous JavaScript function and provides a github argument, which is a pre-authenticated Octokit REST client with pagination helpers. Using this client, you can easily call the Issues API, including the endpoint for creating comments, and encapsulate that logic inside your custom action.
The inputs of the action
We are going to include the following inputs for this action:
| Input | Description |
|---|---|
| gh-token | GitHub token with sufficient permissions to create comments on issues and pull requests. |
| issue-number | Numeric identifier of the issue or pull request where the comment will be posted |
| repository | Target repository in owner/repository format where the issue or pull request exists. |
| body | Content of the comment to be posted on the issue or pull request |
The action.yml:
name: github-issue-commenter
description: Create a comment on a GitHub issue or pull request.
inputs:
gh-token:
required: true
description: GitHub token to authenticate with the GitHub API.
issue-number:
required: true
description: The issue or pull request number to comment on.
comment-body:
required: true
description: The body of the comment to create.
repository:
required: true
description: The repository in the format 'owner/repo'.
runs:
using: "composite"
steps:
- name: Create GitHub issue comment
id: create-issue-comment
uses: actions/github-script@v7
with:
github-token: ${{ inputs.gh-token }}
script: |
const [owner, repo] = "${{ inputs.repository }}".split('/');
if (!owner || !repo) {
core.setFailed('Invalid repository format. Expected "owner/repo".');
}
const issueNumber = parseInt("${{ inputs.issue-number }}", 10);
if (isNaN(issueNumber) || issueNumber <= 0) {
core.setFailed('Invalid issue number. It must be a positive integer.');
}
const commentBody = '${{ inputs.comment-body }}';
if (!commentBody) {
core.setFailed('Comment body cannot be empty.');
}
console.log(`Creating comment on issue/pr #${issueNumber} in ${owner}/${repo}`);
try {
const response = await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: commentBody
});
console.log(`Comment created: ${response.data.html_url}`);
} catch (error) {
console.log('An error occurred while creating the comment:', error);
core.setFailed('Failed to create comment on the issue or pull request.');
}
As mentioned, the action uses actions/github-script@v7, which supplies a preconfigured Octokit client (github) and the Actions toolkit (core). Before interacting with the API, the script performs strict input validation to fail fast with deterministic error messages: it splits the repository string to extract owner and repo, rejecting invalid formats; it parses and validates issue-number as a strictly positive integer; and it checks that comment-body is non-empty. Any invalid state results in core.setFailed(…), preventing unnecessary API calls and surfacing clear diagnostics in the workflow logs.
Once inputs are validated, the action logs the target resource and executes github.rest.issues.createComment, which invokes the POST /repos/{owner}/{repo}/issues/{issue_number}/comments endpoint with the provided body. A successful response confirms the operation and prints the created comment’s html_url for traceability. If the API call fails—due to permission issues, non-existent issue/PR, or other server/client errors—the error is logged and the step is explicitly marked as failed via core.setFailed, ensuring downstream jobs or steps can reliably detect and react to the failure state.
This action can be called, as expected, from a GitHub Actions workflow. A previous job there can create a matrix, for instance, to create different jobs for each issue.