Tutorials Automations
CI Reviewer Bot
This tutorial shows how to build a CI reviewer bot that runs an Autohand agent on every pull request and posts the review as a comment.
What you will build
- A GitHub Actions workflow that triggers on pull requests.
- An Autohand agent that reviews changed files.
- A script that posts the review back to GitHub.
Prerequisites
- A GitHub repository with Actions enabled.
- An Autohand API key stored as
AUTOHAND_API_KEY. - A GitHub token with pull request comment permissions.
Step 1: create the workflow
GitHub-hosted runners have no stored Autohand sign-in, so the workflow runs Autohand with --bare and sets AUTOHAND_PROVIDER, AUTOHAND_API_KEY, and AUTOHAND_AI_API_KEY. Without --bare, the CLI waits for a browser sign-in and the job hangs. See Authenticate in CI and containers.
name: Autohand Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Autohand
run: curl -fsSL https://autohand.ai/install.sh | bash
- name: Run review
env:
AUTOHAND_PROVIDER: autohandai
AUTOHAND_API_KEY: ${{ secrets.AUTOHAND_API_KEY }}
AUTOHAND_AI_API_KEY: ${{ secrets.AUTOHAND_API_KEY }}
run: |
autohand --bare -p "Review the diff in this pull request. Focus on bugs, security, and clarity." --restricted --json local > review.json
- name: Post comment
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/post-review.js review.jsonStep 2: post the review
With --json local, review.json holds one object: {"type":"result","content":"..."} on success, or {"type":"error","message":"..."} on failure. The post step reads content and creates a PR comment. Use the language already available in your CI runner, or call the GitHub API directly with curl.
GitHub Actions
name: Autohand Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: curl -fsSL https://autohand.ai/install.sh | sh
- run: autohand --bare -p "Review this pull request" --restricted --json local > review.json
env:
AUTOHAND_PROVIDER: autohandai
AUTOHAND_API_KEY: ${{ secrets.AUTOHAND_API_KEY }}
AUTOHAND_AI_API_KEY: ${{ secrets.AUTOHAND_API_KEY }}
JavaScript
import fs from 'node:fs/promises';
import { Octokit } from '@octokit/rest';
const review = JSON.parse(await fs.readFile(process.argv[2], 'utf8'));
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
await new Octokit({ auth: process.env.GITHUB_TOKEN }).rest.issues.createComment({
owner,
repo,
issue_number: Number(process.env.PR_NUMBER),
body: review.content
});
TypeScript
import fs from 'node:fs/promises';
import { Octokit } from '@octokit/rest';
type Review = { type: string; content: string };
const review = JSON.parse(await fs.readFile(process.argv[2], 'utf8')) as Review;
const [owner, repo] = process.env.GITHUB_REPOSITORY!.split('/');
await new Octokit({ auth: process.env.GITHUB_TOKEN }).rest.issues.createComment({
owner,
repo,
issue_number: Number(process.env.PR_NUMBER),
body: review.content
});
Python
import json
import os
import requests
import sys
owner, repo = os.environ["GITHUB_REPOSITORY"].split("/")
review = json.load(open(sys.argv[1]))
requests.post(
f"https://api.github.com/repos/{owner}/{repo}/issues/{os.environ['PR_NUMBER']}/comments",
headers={"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}"},
json={"body": review["content"]},
)
Go
body, _ := os.ReadFile(os.Args[1])
var review struct{ Content string `json:"content"` }
json.Unmarshal(body, &review)
client := github.NewTokenClient(ctx, os.Getenv("GITHUB_TOKEN"))
owner, repo := splitRepo(os.Getenv("GITHUB_REPOSITORY"))
client.Issues.CreateComment(ctx, owner, repo, prNumber(), &github.IssueComment{
Body: github.String(review.Content),
})
Java
String review = Files.readString(Path.of(args[0]));
String body = Json.parse(review).getString("content");
String[] repo = System.getenv("GITHUB_REPOSITORY").split("/");
github.createIssueComment(
repo[0],
repo[1],
Integer.parseInt(System.getenv("PR_NUMBER")),
body
);
Swift
let review = try JSONDecoder().decode(Review.self, from: Data(contentsOf: URL(fileURLWithPath: CommandLine.arguments[1])))
let repository = ProcessInfo.processInfo.environment["GITHUB_REPOSITORY"]!.split(separator: "/")
try await github.createIssueComment(
owner: String(repository[0]),
repo: String(repository[1]),
number: Int(ProcessInfo.processInfo.environment["PR_NUMBER"]!)!,
body: review.content
)
curl
curl -X POST \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
-d "$(jq -n --arg body "$(jq -r .content review.json)" '{body:$body}')" Step 3: tune the prompt
Iterate on the prompt to match your team's review style. Include context such as coding standards, test expectations, and areas to ignore.
Next steps
Add a check that fails the build when the agent finds blocking issues, or extend the bot to suggest code changes.