npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mshick/add-pr-comment

v3.11.0

Published

A GitHub Action which adds a comment to a pull request issue or commit.

Readme

add-pr-comment

All Contributors

CI Check dist/ CodeQL Coverage OpenSSF Baseline

A GitHub Action which adds a comment to a pull request issue or commit.

This action also works on issue, issue_comment, deployment_status, push and any other event where an issue can be found directly on the payload or via a commit sha.

Features

  • Modify issues for PRs merged to main.
  • By default will post "sticky" comments. If on a subsequent run the message text changes the original comment will be updated.
  • Multiple sticky comments allowed by setting unique message-ids.
  • Optional message overrides based on job status.
  • Multiple posts to the same conversation optionally allowable.
  • Supports a proxy for fork-based PRs. See below.
  • Supports creating a message from a file path.
  • Supports file attachments via GitHub Artifacts.
  • Automatic message truncation for oversized messages (e.g., large Terraform plans).
  • Supports commit comments in addition to PR/issue comments.
  • Supports template variables like %NOW% for dynamic content in messages.
  • Available as a library for use in custom actions and scripts.

Usage

Note that write access needs to be granted for the pull-requests scope.

on:
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        with:
          message: |
            **Hello**
            🌏
            !

You can even use it on PR Issues that are related to PRs that were merged into main, for example:

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        with:
          message: |
            **Hello MAIN**

Configuration options

| Input | Location | Description | Required | Default | | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------- | | message | with | The message you'd like displayed, supports Markdown and all valid Unicode characters. | maybe | | | message-path | with | Path to a message you'd like displayed. Will be read and displayed just like a normal message. Supports multi-line input and globs. Multiple messages will be concatenated. | maybe | | | message-success | with | A message override, printed in case of success. | no | | | message-failure | with | A message override, printed in case of failure. | no | | | message-cancelled | with | A message override, printed in case of cancelled. | no | | | message-skipped | with | A message override, printed in case of skipped. | no | | | status | with | Required if you want to use message status overrides. | no | {{ job.status }} | | repo-owner | with | Owner of the repo. | no | {{ github.repository_owner }} | | repo-name | with | Name of the repo. | no | {{ github.event.repository.name }} | | repo-token | with | Valid GitHub token, either the temporary token GitHub provides or a personal access token. | no | {{ github.token }} | | message-id | with | Message id to use when searching existing comments. If found, updates the existing (sticky comment). | no | | | delete-on-status | with | If specified and a comment exists and the status is matching the value of this option, the comment will be deleted | no | | | refresh-message-position | with | Should the sticky message be the last one in the PR's feed. | no | false | | allow-repeats | with | Boolean flag to allow identical messages to be posted each time this action is run. | no | false | | proxy-url | with | String for your proxy service URL if you'd like this to work with fork-based PRs. | no | | | issue | with | Optional issue number override. | no | | | update-only | with | Only update the comment if it already exists. | no | false | | GITHUB_TOKEN | env | Valid GitHub token, can alternatively be defined in the env. | no | | | preformatted | with | Treat message text as pre-formatted and place it in a codeblock | no | | | template-variables | with | Enable template variable expansion in messages (e.g. %NOW%, %NOW:yyyy-MM-dd%). | no | false | | find | with | Patterns to find in an existing message and replace with either replace text or a resolved message. See Find-and-Replace for more detail. | no | | | replace | with | Strings to replace a found pattern with. Each new line is a new replacement, or if you only have one pattern, you can replace with a multiline string. | no | | | attach-path | with | A file path or glob pattern for files to upload as artifacts and link in the comment. See File Attachments. | no | | | attach-name | with | Name for the uploaded artifact. | no | pr-comment-attachments | | attach-text | with | Markdown content for the attachment section. Always separated from the comment by a horizontal rule. Supports %ARTIFACT_URL% and %ATTACH_NAME% template variables. | no | (see File Attachments) | | truncate | with | Truncation mode when the message exceeds the safe comment length. See Message Truncation. | no | artifact | | comment-target | with | Where to post the comment. Use pr for pull request/issue comments or commit for commit comments. See Commit Comments. | no | pr | | commit-sha | with | The commit SHA to comment on when comment-target is commit. Defaults to the current commit. | no | {{ github.sha }} |

Outputs

| Output | Description | | ----------------- | ----------------------------------------------------------------- | | comment-created | "true" if a new comment was created, "false" otherwise. | | comment-updated | "true" if an existing comment was updated, "false" otherwise. | | comment-id | The numeric ID of the created or updated comment. | | artifact-url | If files were attached, the URL to download the artifact. | | truncated | "true" if the message was truncated, "false" otherwise. | | truncated-artifact-url | If truncated in artifact mode, the URL to download the full message. |

Using outputs in subsequent steps

- uses: mshick/add-pr-comment@v3
  id: comment
  with:
    message: 'Hello world'

- name: Check outputs
  run: |
    echo "Comment created: ${{ steps.comment.outputs.comment-created }}"
    echo "Comment updated: ${{ steps.comment.outputs.comment-updated }}"
    echo "Comment ID: ${{ steps.comment.outputs.comment-id }}"

Tip: By default, comments are "upsert" — a comment is created on the first run and updated on subsequent runs when matched by message-id. If you want this create-or-update behavior, you do not need to set update-only. Setting update-only: true skips comment creation entirely and only updates an existing comment. Use it when you specifically want no comment to appear unless one was already posted by a previous step or run.

Advanced Uses

Proxy for Fork-based PRs

GitHub limits GITHUB_TOKEN and other API access token permissions when creating a PR from a fork. This precludes adding comments when your PRs are coming from forks, which is the norm for open source projects. To work around this situation I've created a simple companion app you can deploy to Cloud Run or another host to proxy the create comment requests with a personal access token you provide.

See this issue: https://github.community/t/github-actions-are-severely-limited-on-prs/18179/4 for more details.

Check out the proxy service here: https://github.com/mshick/add-pr-comment-proxy

Example

on:
  pull_request:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        with:
          message: |
            **Howdie!**
          proxy-url: https://add-pr-comment-proxy-94idvmwyie-uc.a.run.app

Status Message Overrides

You can override your messages based on your job status. This can be helpful if you don't anticipate having the data required to create a helpful message in case of failure, but you still want a message to be sent to the PR comment.

Example

on:
  pull_request:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        if: always()
        with:
          message: |
            **Howdie!**
          message-failure: |
            Uh oh!

Multiple Message Files

Instead of directly setting the message you can also load a file with the text of your message using message-path. message-path supports loading multiple files and files on multiple lines, the contents of which will be concatenated.

Example

on:
  pull_request:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        if: always()
        with:
          message-path: |
            message-part-*.txt

Find-and-Replace

Patterns can be matched and replaced to update comments. This could be useful for some situations, for instance, updating a checklist comment.

Find is a regular expression passed to the RegExp() constructor. You can also include modifiers to override the default gi.

Example

Original message:

[ ] Hello
[ ] World

Action:

on:
  pull_request:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        if: always()
        with:
          find: |
            \n\\[ \\]
          replace: |
            [X]

Final message:

[X] Hello
[X] World

Multiple find and replaces can be used:

Example

Original message:

hello world!

Action:

on:
  pull_request:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        if: always()
        with:
          find: |
            hello
            world
          replace: |
            goodnight
            moon

Final message:

goodnight moon!

It defaults to your resolved message (either from message or message-path) to do a replacement:

Example

Original message:

hello

<< FILE_CONTENTS >>

world

Action:

on:
  pull_request:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        if: always()
        with:
          message-path: |
            message.txt
          find: |
            << FILE_CONTENTS >>

Final message:

hello

secret message from message.txt

world

File Attachments

You can attach files to your PR comments by uploading them as GitHub Artifacts and embedding download links in the comment body. Files matching the attach-path glob are uploaded as a single artifact, and a markdown section with the download link is appended to your comment, separated by a horizontal rule.

Note: Artifact download URLs require GitHub authentication and expire based on your repository's retention settings (default 90 days). Images will not render inline — they appear as download links. This is a GitHub platform limitation.

Simple — attach a file with defaults

on:
  pull_request:

jobs:
  report:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - run: echo "Build output here" > report.txt
      - uses: mshick/add-pr-comment@v3
        with:
          message: |
            Build complete! See attached report.
          attach-path: report.txt

Advanced — glob pattern, custom name, and custom text template

on:
  pull_request:

jobs:
  report:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - run: |
          mkdir -p coverage
          echo "line coverage: 85%" > coverage/summary.txt
          echo "<html>...</html>" > coverage/report.html
      - uses: mshick/add-pr-comment@v3
        with:
          message: |
            ## Coverage Report
            Tests passed with 85% line coverage.
          attach-path: coverage/*
          attach-name: coverage-report
          attach-text: '📎 [Download %ATTACH_NAME%](%ARTIFACT_URL%)'

The attach-text input supports two template variables:

| Variable | Replaced with | | ---------------- | ---------------------------------- | | %ARTIFACT_URL% | The artifact download URL | | %ATTACH_NAME% | The value of the attach-name input |

Message Truncation

GitHub's API limits comment bodies to 65,536 characters. Messages that exceed this limit (common with large Terraform plans, verbose test output, etc.) would previously cause the action to fail with an "Argument list too long" or API error.

This action automatically truncates oversized messages to stay within a safe limit (61,440 characters, which includes a 4,096 character buffer). The truncate input controls what happens with the full message:

| Mode | Behavior | | ---- | -------- | | artifact (default) | The full, untruncated message is uploaded as a downloadable GitHub Artifact. The comment is truncated and a download link is appended. | | simple | The comment is truncated and a notice is appended. No artifact is uploaded. |

If artifact upload fails (e.g., permissions, network issues), the action automatically falls back to simple truncation.

Example — default artifact mode

on:
  pull_request:

jobs:
  plan:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - run: terraform plan -no-color > plan.txt
      - uses: mshick/add-pr-comment@v3
        with:
          message-path: plan.txt

If the plan output exceeds the safe limit, the comment will be truncated and end with:

This message was truncated. Download full message

Example — simple mode (no artifact)

- uses: mshick/add-pr-comment@v3
  with:
    message-path: plan.txt
    truncate: simple

The comment will be truncated and end with:

This message was truncated.

Using the truncation outputs

- uses: mshick/add-pr-comment@v3
  id: comment
  with:
    message-path: plan.txt

- name: Check if truncated
  if: steps.comment.outputs.truncated == 'true'
  run: |
    echo "Message was truncated"
    echo "Full message: ${{ steps.comment.outputs.truncated-artifact-url }}"

Tip: For very large outputs like Terraform plans, prefer using message-path over the message input. The message input is passed via environment variables, which have OS-level size limits that can cause failures before the action even runs. File-based input via message-path avoids this entirely.

Commit Comments

Instead of posting to a pull request or issue, you can post comments directly on a commit. This is useful for workflows triggered by push events or when you want feedback attached to a specific commit rather than a PR conversation.

Example

on:
  push:
    branches:
      - main

jobs:
  notify:
    runs-on: ubuntu-latest
    steps:
      - uses: mshick/add-pr-comment@v3
        with:
          comment-target: commit
          message: |
            Build succeeded for ${{ github.sha }}

You can also specify a different commit SHA:

- uses: mshick/add-pr-comment@v3
  with:
    comment-target: commit
    commit-sha: ${{ github.event.before }}
    message: |
      Comparing changes since this commit.

Note: Commit comments use a different GitHub API than issue/PR comments. Sticky comments (message-id), update-only, refresh-message-position, and delete-on-status all work with commit comments. The proxy-url option is not supported for commit comments.

Important: The commit comment target requires that commit comments are enabled on your repository. GitHub now allows repository admins to disable comments on individual commits. If commit comments are disabled, this action will fail when using comment-target: commit.

Bring your own issues

You can set an issue id explicitly. Helpful for cases where you want to post to an issue but for some reason the event would not allow the id to be determined.

Example

In this case add-pr-comment should have no problem finding the issue number on its own, but for demonstration purposes.

on:
  deployment_status:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - id: pr
        run: |
          issue=$(gh pr list --search "${{ github.sha }}" --state open --json number --jq ".[0].number")
          echo "issue=$issue" >>$GITHUB_OUTPUT
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - uses: mshick/add-pr-comment@v3
        with:
          issue: ${{ steps.pr.outputs.issue }}
          message: |
            **Howdie!**

Delete on status

This option can be used if comment needs to be removed if a status is reached.

Example

Here, a comment will be added on failure, but on a subsequent run, if the job reaches success status, the comment will be deleted.

on:
  pull_request:

jobs:
  pr:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        if: always()
        with:
          message-failure: There was a failure
          delete-on-status: success

Template Variables

Messages support template variables that are replaced with dynamic values at runtime. Template variables use the %VARIABLE% syntax and must be enabled by setting template-variables: true.

%NOW% — Current Date/Time

Inserts the current date and time. By default it produces an ISO 8601 timestamp. You can customize the format inline using date-fns format strings:

%NOW%                → 2026-04-23T14:32:01.000Z
%NOW:yyyy-MM-dd%     → 2026-04-23
%NOW:HH:mm:ss%       → 14:32:01
%NOW:MMM d, yyyy%    → Apr 23, 2026

The format string goes between the colon and the closing %. Any valid date-fns format pattern works.

Example — timestamp in a sticky comment

on:
  pull_request:

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - uses: mshick/add-pr-comment@v3
        with:
          template-variables: true
          message: |
            ### Deploy Preview Ready

            | Detail | Value |
            | ------ | ----- |
            | Status | Live |
            | Updated | %NOW:MMM d, yyyy 'at' h:mm a% |

Example — simple date footer

- uses: mshick/add-pr-comment@v3
  with:
    template-variables: true
    message: |
      Tests passed!
      <sub>Last run: %NOW:yyyy-MM-dd HH:mm:ss% UTC</sub>

Note: Template variables are expanded after find-and-replace processing, so %NOW% tokens in replacement text will also be expanded. If an invalid format string is provided, the token is left as-is and a warning is logged.

Programmatic Usage

This package also exports its core functions as a library, so you can use them in your own custom GitHub Actions or scripts.

npm add @mshick/add-pr-comment
import {
  createComment,
  getExistingComment,
  updateComment,
  deleteComment,
  createCommitComment,
  getMessage,
  truncateMessage,
  uploadAttachments,
} from '@mshick/add-pr-comment'

The library exports functions for managing both issue/PR comments and commit comments, file discovery, message resolution, truncation, attachments, and proxy support. Type definitions are included.

Security

Version Pinning

There are three ways to reference this action, from most to least secure:

  1. Commit SHA (most secure): uses: mshick/add-pr-comment@ffd016c7e151d97d69d21a843022fd4cd5b96fe5 — immutable, can never change.
  2. Semver tag (recommended): uses: mshick/[email protected] — protected by tag rulesets, cannot be moved or deleted once created.
  3. Major version tag (convenient but less secure): uses: mshick/add-pr-comment@v3 — floating tag that is updated on each release to point to the latest version. While convenient, floating tags are a potential security risk as they could theoretically be re-pointed to a different commit.

For maximum security, pin to a full semver tag or commit SHA. Semver tags (e.g., v3.9.0) in this repository are protected by GitHub tag rulesets and cannot be modified after creation.

Releases include build provenance attestations generated by actions/attest-build-provenance, which can be used to verify that the release was produced by the official CI pipeline.

Contributors ✨

Thanks goes to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!