20

I am trying to get a regular comment inserted in a pull request from a GitHub action. I can't seem to get it right. Octokit, the underlying library, allows you to create reviewComments to PRs, but those refer to a commit and that's not what I want, I want a simple comment. I figured I can just use octokit.issues.createComment. However, that does not seem to work. Here's the code

import * as core from '@actions/core';
const {GitHub, context} = require('@actions/github')
const parse = require('parse-diff')

async function run() {
    try {
        // get information on everything
        const token = core.getInput('github-token', {required: true})
        const github = new GitHub(token, {} )
        console.log( context )
        const PR_number = context.payload.pull_request.number

        // Check if the body contains required string
        const bodyContains = core.getInput('bodyContains')

        if ( context.payload.pull_request.body.indexOf( bodyContains) < 0  ) {
            core.setFailed("The body of the PR does not contain " + bodyContains);
            console.log( "Actor " + context.actor + " pr number " PR_number)
            const result = await github.issues.createComment({
                owner: context.actor,
                repo: context.payload.repository.full_name,
                issue_number: PR_number,
                body: "We need to have the word " + bodyContains + " in the body of the pull request"
            });
            console.log(result)
       } // more irrelevant stuff below
}}

This simply seems to retur "Not found". I can't seem to be able to find out if it's a type problem, or something different. Theoretically, owner, repo, and issue number, as well as body, should be right, and it's printed correctly. Any help will be appreciated. This is probably a more general question in the realm of GitHub API, with GitHub actions being simply the context, so I might be wrong there.

jjmerelo
  • 21,126
  • 6
  • 34
  • 82
  • 1
    I had a similar need and wanted a bot-like comment inserted by GitHub Actions. I'm going to shamelessly share my repo, which I managed to do the trick with axios and REST call - some example PR and issue are in the repo: https://github.com/rytswd/respost/ – Ryota Sep 24 '19 at 15:30

4 Answers4

34

A canonical way is using the official Github Script actions. Don't get confused, issues and PRs are the same for the GitHub API.

2020:

on:
  # Trigger the workflow on push or pull request
  pull_request:
    branches:
      - master
      - develop

jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v3
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: ' Thanks for reporting!'
            })

Seen on: https://github.com/actions/github-script#comment-on-an-issue

EDIT 2021:

Adapting to GH's newer versions, one has to reference the rest client directly. See note as of writing.

on:
  # Trigger the workflow on push or pull request
  pull_request:
    branches:
      - master
      - develop

jobs:
  comment:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v5
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: ' Thanks for reporting!'
            })
Kevin Goedecke
  • 1,198
  • 1
  • 11
  • 24
  • 1
    I don't like this 2020 approach. Touching real API endpoints with `curl` should be the way to do it. Otherwise, this all github actions thingie is useless as a script, as it uses another "script", thus obscuring the intent and jeopardizing itself. As a result, making you dumb, cause you don't know (or have control over, rather) what's going on. – Sevastyan Savanyuk Aug 11 '21 at 11:32
  • 2
    @SevastyanSavanyuk OP's wording could have been less opinionated, but once you are in the GH ecosystem, I think wanting to be less dependent on it is kind of not necessary. Having worked a lot with actions, this is the best way to go imo – eljefedelrodeodeljefe Nov 27 '21 at 14:25
  • 2
    As for "Don't get confused, issues and PRs are the same for the GitHub API.", check https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L55-L62 – yskkin Dec 21 '21 at 01:45
  • hello @kevin-goedecke, the 6th version of actions/github-script is now available, you should update your snippet – Hassan ALAMI Mar 09 '22 at 15:51
  • Shouldn't this be triggered by `pull_request_target` instead of `pull_request`? In a run triggered by `pull_request`, `GITHUB_TOKEN` doesn't have write access to the repository and causes the error 'Resource not accessible by integration'. – David Lechner Mar 30 '22 at 17:20
21

I initially tried to use Respost, but it doesn't allow setting a raw body string.

So here's a way to do it with curl.

In a .github/workflows/whatever.yml:

name: Some workflow

on:
  issue_comment:
    types: [created]

jobs:
  comment:
    if: contains(github.event.comment.body, 'special string')

    runs-on: ubuntu-latest

    steps:
      - name: Add comment to PR
        env:
          URL: ${{ github.event.issue.comments_url }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          curl \
            -X POST \
            $URL \
            -H "Content-Type: application/json" \
            -H "Authorization: token $GITHUB_TOKEN" \
            --data '{ "body": "blah blah" }'
slow
  • 730
  • 7
  • 14
  • 5
    For the event `pull_request` I had to use `${{ github.event.pull_request.comments_url }}` for the URL – binaryfunt Jul 06 '20 at 21:44
  • for help with string substitution see here for single/double quotes https://stackoverflow.com/questions/13799789/expansion-of-variables-inside-single-quotes-in-a-command-in-bash – bubbaspike Sep 15 '21 at 15:59
8

You can also use the @actions/github which will permit you to use an octokit client to simplify the actions :

const core = require('@actions/core');
const github = require('@actions/github');

async function run() {
  try {
    const message = core.getInput('message');
    const github_token = core.getInput('GITHUB_TOKEN');

    const context = github.context;
    if (context.payload.pull_request == null) {
        core.setFailed('No pull request found.');
        return;
    }
    const pull_request_number = context.payload.pull_request.number;

    const octokit = new github.GitHub(github_token);
    const new_comment = octokit.issues.createComment({
        ...context.repo,
        issue_number: pull_request_number,
        body: message
      });

  } catch (error) {
    core.setFailed(error.message);
  }
}

run();

Taken from this repo.

Térence
  • 339
  • 3
  • 4
  • Here's a demo as standalone workflow: [workflow.yml](https://github.com/cirosantilli/china-dictatorship/blob/97408314e85673e20c10c1232de8d46f8cdd6004/.github/workflows/issue.yml), [main.js](https://github.com/cirosantilli/china-dictatorship/blob/97408314e85673e20c10c1232de8d46f8cdd6004/action.js). But yeah, just use `actions/github-script` most likely: https://stackoverflow.com/a/64126737/895245 – Ciro Santilli Путлер Капут 六四事 Apr 19 '21 at 19:50
4

The other answers don't mention is the security restrictions of a GitHub action run from a fork that triggers the pull_request event. The GITHUB_TOKEN in these actions does not have write access to the repository and therefore cannot create a comment. See permissions for the GITHUB_TOKEN.

The GitHub docs for the workflow_run event have a good example of how to work around this. The basic idea is to have the workflow that is triggered by the pull_request event upload any info needed in the comment as a build artifact using actions/upload-artifact. Then a separate workflow triggered by a workflow_run event downloads the information using actions/download-artifact.

NOTE: For security, since the workflow triggered by workflow_run has write access, it must be committed to the default branch before it can be used. (Also keep in mind that the build artifacts could contain malicious data from a malicious pull request).

Here is a copy of the example workflows from the linked docs (in case the link breaks or the docs change):

name: Upload data

on:
  pull_request:

jobs:
  upload:
    runs-on: ubuntu-latest

    steps:        
      - name: Save PR number
        env:
          PR_NUMBER: ${{ github.event.number }}
        run: |
          mkdir -p ./pr
          echo $PR_NUMBER > ./pr/pr_number
      - uses: actions/upload-artifact@v3
        with:
          name: pr_number
          path: pr/
name: Use the data

on:
  workflow_run:
    workflows: [Upload data]
    types:
      - completed

jobs:
  download:
    runs-on: ubuntu-latest
    steps:
      - name: 'Download artifact'
        uses: actions/github-script@v5
        with:
          script: |
            let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
               owner: context.repo.owner,
               repo: context.repo.repo,
               run_id: context.payload.workflow_run.id,
            });
            let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
              return artifact.name == "pr_number"
            })[0];
            let download = await github.rest.actions.downloadArtifact({
               owner: context.repo.owner,
               repo: context.repo.repo,
               artifact_id: matchArtifact.id,
               archive_format: 'zip',
            });
            let fs = require('fs');
            fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr_number.zip`, Buffer.from(download.data));

      - name: 'Unzip artifact'
        run: unzip pr_number.zip

      - name: 'Comment on PR'
        uses: actions/github-script@v5
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            let fs = require('fs');
            let issue_number = Number(fs.readFileSync('./pr_number'));
            await github.rest.issues.createComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: issue_number,
              body: 'Thank you for the PR!'
            });
David Lechner
  • 1,052
  • 14
  • 23