Skip to content

@dvdsgl /Procfile

Embed URL


Subversion checkout URL

You can clone with
Download ZIP
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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.