Create or Update Pull Request Comments in GitHub

Posted on

Oftentimes, it’s nice to have a bot user post some information to a Pull Request. For example, imagine a link to a pre-production preview link for the branch, or infrastructure-as-code diffs. Having this data in-line with your Pull Request makes it simpler for reviewers to understand the impact and risk of approving a given change.

Sadly, GitHub doesn’t provide an easy way to “create or update” a comment for a particular bot message. So, I built a simple serverless solution to work around this using the GitHub GraphQL API.

The Solution

I worked around this problem by building a serverless API with AWS API Gateway. It communicates with the GitHub GraphQL API to update an existing comment or create a new one.

So, for instance, a call to the serverless API looks something like this:

PULL_REQUEST_URL="https://github.com/blimmer/example/pull/1"
MESSAGE="The current date is $(date). This comment will be updated."
curl \
  -H 'Content-Type: application/json' \
  -X PUT \
  -d "{ \"url\": \"$PULL_REQUEST_URL\", \"body\": \"$MESSAGE\", \"singletonId\": \"singleton-demo\" }" \
  'https://github-pr-comment-api.benlimmer.com/comment'

When calling the API endpoint, you pass the singletonId value. This informs the backend that you want to update any existing comment with the same singletonId. Then, if an existing comment with that singletonId is discovered, the content is updated with the new message.

The key to this solution is an HTML comment inside the GitHub comment text. This is how I identify whether we should create a new comment or update an existing one.

HTML comment containing identifiable metadata to identify existing comments

So, when a request to the endpoint is received, I fetch existing comments on the Pull Request:

query GetCommentsForPullRequest($url: URI!) {
  resource(url: $url) {
    ... on PullRequest {
      comments(first: 100) {
        nodes {
          id
          url
          author {
            login
          }
          body
        }
        pageInfo {
          hasNextPage
        }
      }
    }
  }
}

Then, I search the comment body for the HTML comment with the singletonId passed. If it exists, I update the comment via this mutation:

mutation UpdatePullRequestComment($commentId: ID!, $body: String!) {
  updateIssueComment(input: { id: $commentId, body: $body }) {
    issueComment {
      id
    }
  }
}

Otherwise, I simply create a new comment via:

mutation CreateNewPullRequestComment($prId: ID!, $body: String!) {
  addComment(input: { subjectId: $prId, body: $body }) {
    commentEdge {
      node {
        id
      }
    }
  }
}

Demo

To see this solution in action, check out the Demo Pull Request.

As you’ll see, the first time the API is called, a new comment is created. Then, when the API is subsequently called, the existing comment will be updated.

By updating an existing comment, bot-based noise is significantly reduced, while the history of each bot post is retained in the “edited” dropdown.

changes to the pull request comment are retained in the edited dropdown
the edit history displays inline diffs when the comment is updated

The demo PR is interactive! Add a comment to see the bot update the existing comment with a new timestamp.

Related Post

If you’re looking to build against the GitHub GraphQL API in TypeScript, check out my post “Adding Typescript Types to Github’s GraphQL API”. This post describes how to gain type-safety when working with the GraphQL API.

Find an issue?
Open a pull request against my blog on GitHub.
Ben Limmer