Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Automated Jenkins Comments on GitHub Pull Requests

Configure the post-build hook and launch it on Heroku:

$ git clone git:// jenkins-comments
$ cd jenkins-comments
$ heroku create --stack cedar
$ heroku config:add NODE_ENV=production
$ heroku config:add GITHUB_USER_LOGIN=...
$ heroku config:add GITHUB_USER_PASSWORD=...
$ heroku config:add JENKINS_URL=...
$ git push heroku master
$ heroku ps:scale web=1

Then configure your Jenkins job to call the post-build hook to report job status:

$ curl "\

You'll have to specify GITHUB_USER, GITHUB_REPO, and your build should set BUILD_STATUS=success if the build succeeded.

async = require 'async'
request = require 'request'
express = require 'express'
_ = require 'underscore'
_s = require 'underscore.string'
class PullRequestCommenter
BUILDREPORT = "**Build Status**:"
constructor: (@sha, @job, @user, @repo, @succeeded) ->
@job_url = "#{process.env.JENKINS_URL}/job/#{@repo}/#{@job}"
@api = "https://#{process.env.GITHUB_USER_LOGIN}:#{process.env.GITHUB_USER_PASSWORD}{@user}/#{@repo}"
post: (path, obj, cb) => { uri: "#{@api}#{path}", json: obj }, (e, r, body) ->
cb e, body
get: (path, cb) =>
request.get { uri: "#{@api}#{path}", json: true }, (e, r, body) ->
cb e, body
del: (path, cb) =>
request.del { uri: "#{@api}#{path}" }, (e, r, body) ->
cb e, body
getCommentsForIssue: (issue, cb) =>
@get "/issues/#{issue}/comments", cb
deleteComment: (id, cb) =>
@del "/issues/comments/#{id}", cb
getPulls: (cb) =>
@get "/pulls", cb
getPull: (id, cb) =>
@get "/pulls/#{id}", cb
commentOnIssue: (issue, comment) =>
@post "/repos/#{@user}/#{@repo}/issues/#{issue}/comments", (body: comment), (e, body) ->
console.log e if e?
successComment: ->
"#{BUILDREPORT} `Succeeded` (#{@sha}, [job info](#{@job_url}))"
errorComment: ->
"#{BUILDREPORT} `Failed` (#{@sha}, [job info](#{@job_url}))"
# Find the first open pull with a matching HEAD sha
findMatchingPull: (pulls, cb) =>
pulls = _.filter pulls, (p) => p.state is 'open'
async.detect pulls, (pull, detect_if) =>
@getPull pull.number, (e, { head }) =>
return cb e if e?
detect_if head.sha is @sha
, (match) =>
return cb "No pull request for #{@sha} found" unless match?
cb null, match
removePreviousPullComments: (pull, cb) =>
@getCommentsForIssue pull.number, (e, comments) =>
return cb e if e?
old_comments = _.filter comments, ({ body }) -> _s.include body, BUILDREPORT
async.forEach old_comments, (comment, done_delete) =>
@deleteComment, done_delete
, () -> cb null, pull
makePullComment: (pull, cb) =>
comment = if @succeeded then @successComment() else @errorComment()
@commentOnIssue pull.number, comment
updateComments: (cb) ->
async.waterfall [
], cb
app = module.exports = express.createServer()
app.configure 'development', ->
app.set "port", 3000
app.configure 'production', ->
app.use express.errorHandler()
app.set "port", parseInt process.env.PORT
# Jenkins lets us know when a build has failed or succeeded.
app.get '/jenkins/post_build', (req, res) ->
sha = req.param 'sha'
job = parseInt req.param 'job'
user = req.param 'user'
repo = req.param 'repo'
succeeded = req.param('status') is 'success'
# Look for an open pull request with this SHA and make comments.
commenter = new PullRequestCommenter sha, job, user, repo, succeeded
commenter.updateComments (e, r) -> console.log e if e?
res.send 'Ok', 200
app.listen app.settings.port
"name": "jenkins-pull-request-commenter"
, "version": "0.0.1"
, "private": true
, "dependencies": {
"express": "2.5.7"
, "jade": ">= 0.0.1"
, "coffee-script": "1.2.x"
, "underscore": "*"
, "underscore.string": "*"
, "request": "*"
, "async": "*"
web: node server.js

This comment has been minimized.

Copy link

justincampbell commented Apr 30, 2012

We modified this to store Jenkins' results in Redis and insta-comment when a pull is opened if you're interested:


This comment has been minimized.

Copy link
Owner Author

dvdsgl commented Apr 30, 2012

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.