Skip to content
GitHub stars

Review Hooks

roborev runs reviews in the background. Without hooks, you find out about results by checking the TUI or running roborev status. Hooks close that gap — they run shell commands automatically when reviews finish, so you can get notified, create issues, update dashboards, or trigger downstream workflows without polling.

Common use cases:

  • Desktop notifications so you know when a review is ready
  • Issue creation when reviews find problems (GitHub/GitLab/Jira via shell)
  • Beads integration to auto-create tracked issues for review failures and findings (built-in, zero config)
  • Chat notifications to Slack, Discord, or Teams channels
  • Logging review outcomes to a file or external service
  • CI-like workflows that run scripts when reviews pass or fail

Quick Start

The simplest useful hook is a desktop notification. Add this to ~/.roborev/config.toml to get notified whenever any review finishes:

# macOS
[[hooks]]
event = "review.completed"
command = "osascript -e 'display notification \"Review done for {repo_name} ({sha}): {verdict}\" with title \"roborev\"'"
# Linux (requires libnotify)
[[hooks]]
event = "review.completed"
command = "notify-send roborev 'Review done for {repo_name}: {verdict}'"

No daemon restart needed — hooks are picked up via hot-reload.

Configuration

Hooks are configured as [[hooks]] entries in your global ~/.roborev/config.toml or per-repo .roborev.toml. Each hook has an event to match and either a command to run or a built-in type.

[[hooks]]
event = "review.failed"
command = "/path/to/my-script.sh {job_id}"
[[hooks]]
event = "review.completed"
command = "echo {repo_name} {sha} {verdict} >> ~/review-log.txt"
FieldTypeDescription
eventstringEvent pattern to match (see Events)
commandstringShell command to run, with {var} template interpolation
typestringBuilt-in hook type (currently only "beads"). Mutually exclusive with command

Global vs Per-Repo Hooks

Global hooks (~/.roborev/config.toml) fire for every repo. Use these for notifications, logging, or anything that applies everywhere.

Per-repo hooks (.roborev.toml) fire only for that repo. Use these for repo-specific workflows like creating issues in the right tracker.

Both fire for matching events — per-repo hooks run in addition to global hooks, not instead of them. This means you can have a global notification hook and a per-repo issue-creation hook, and both will fire.

# ~/.roborev/config.toml -- fires for all repos
[[hooks]]
event = "review.completed"
command = "echo {repo_name} {sha} {verdict} >> ~/roborev-events.log"
[[hooks]]
event = "review.failed"
command = "echo {repo_name} {sha} {error} >> ~/roborev-events.log"
# ~/code/myproject/.roborev.toml -- fires only for this repo
[[hooks]]
event = "review.failed"
type = "beads"

Events

EventWhen it fires
review.completedA review finishes successfully (verdict is P for pass or F for fail)
review.failedA review job fails after exhausting retries (agent error, timeout, etc.)
review.*Wildcard: matches both review.completed and review.failed

Note the distinction between review.completed with verdict F (the review ran successfully and found issues) and review.failed (the review job itself errored out). Most notification hooks should use review.* to catch both.

Template Variables

Use {var} syntax in your command string to inject event data. Variables are automatically shell-escaped (single-quoted) to prevent injection, so do not wrap placeholders in your own quotes:

# Good -- placeholders are auto-escaped
command = "echo {error}"
# Bad -- wrapping in quotes produces nested quoting
command = 'echo "{error}"'
VariableDescriptionAvailable
{job_id}Review job ID (numeric, not quoted)Always
{repo}Absolute repo pathAlways
{repo_name}Display name for the repoAlways
{sha}Commit SHA or git refAlways
{agent}Agent that ran the review (e.g., codex, claude-code)Always
{verdict}P (pass) or F (fail)review.completed only
{error}Error message describing why the job failedreview.failed only

Variables that aren’t available for a given event type interpolate to an empty string ('').

Built-in: Beads Integration

If you use beads for issue tracking, roborev has a built-in hook type that creates issues automatically when reviews surface problems. This is the recommended way to track review findings without manual triage.

.roborev.toml
[[hooks]]
event = "review.failed"
type = "beads"
[[hooks]]
event = "review.completed"
type = "beads"

What the type = "beads" hook does for each event:

EventVerdictAction
review.failedn/aCreates a priority-1 issue: “Review failed for {repo} ({sha}): run roborev show {job_id}“
review.completedF (fail)Creates a priority-2 issue: “Review findings for {repo} ({sha}): roborev show {job_id} / one-shot fix with roborev fix {job_id}“
review.completedP (pass)No action

The hook runs in the repo’s working directory so bd finds the correct .beads/ database. Issue titles include the roborev show command so you can jump straight to the full review.

Custom Beads Commands

If you want different behavior (different priority, labels, or formatting), write your own beads command using template variables:

[[hooks]]
event = "review.failed"
command = "bd create 'Agent error on {sha}: run roborev show {job_id}' -p 1"
[[hooks]]
event = "review.completed"
command = "test {verdict} = F && bd create 'Review findings on {sha}: roborev show {job_id}' -p 3"

Examples

Desktop Notifications

Get notified when reviews finish so you don’t have to keep checking:

# macOS -- uses built-in osascript
[[hooks]]
event = "review.completed"
command = "osascript -e 'display notification \"Review {verdict} for {repo_name}\" with title \"roborev\"'"
# Linux -- requires libnotify (notify-send)
[[hooks]]
event = "review.completed"
command = "notify-send -u normal roborev 'Review {verdict} for {repo_name} ({sha})'"

Slack Notifications

Post to a Slack channel when reviews fail. Set SLACK_WEBHOOK_URL in your environment:

[[hooks]]
event = "review.failed"
command = """curl -sf -X POST \
-H 'Content-Type: application/json' \
-d '{"text":"roborev: review failed for {repo_name} ({sha}). Run `roborev show {job_id}` for details."}' \
$SLACK_WEBHOOK_URL"""

For both failures and findings, use two hooks:

[[hooks]]
event = "review.completed"
command = """test {verdict} = F && curl -sf -X POST \
-H 'Content-Type: application/json' \
-d '{"text":"roborev: review findings in {repo_name} ({sha}). Run `roborev show {job_id}`."}' \
$SLACK_WEBHOOK_URL"""
[[hooks]]
event = "review.failed"
command = """curl -sf -X POST \
-H 'Content-Type: application/json' \
-d '{"text":"roborev: review failed for {repo_name} ({sha}). Run `roborev show {job_id}`."}' \
$SLACK_WEBHOOK_URL"""

GitHub Issues

Create a GitHub issue when a review finds problems. Requires the gh CLI:

[[hooks]]
event = "review.failed"
command = "gh issue create --title 'roborev: review failed for {sha}' --body 'Review job {job_id} failed. Run `roborev show {job_id}` for details.' --label bug"

Event Logging

Log all review outcomes to a file for auditing or metrics:

# Global -- logs events from all repos
[[hooks]]
event = "review.completed"
command = "printf '%s %s %s %s %s\\n' $(date -Iseconds) {repo_name} {sha} {agent} {verdict} >> ~/.roborev/events.log"
[[hooks]]
event = "review.failed"
command = "printf '%s %s %s %s %s\\n' $(date -Iseconds) {repo_name} {sha} {agent} {error} >> ~/.roborev/events.log"

Running a Script

For complex logic, point the hook at a script. The working directory is the repo, so you have access to the git tree:

[[hooks]]
event = "review.completed"
command = "~/.roborev/hooks/on-review.sh {job_id} {verdict} {sha}"
~/.roborev/hooks/on-review.sh
#!/usr/bin/env bash
job_id=$1 verdict=$2 sha=$3
if [ "$verdict" = "F" ]; then
roborev show --job "$job_id" >> ~/reviews/findings.log
fi

Conditional Hooks

Shell features work in commands since they run via sh -c. Use &&, ||, or test to add conditions:

# Only notify on failures, not passes
[[hooks]]
event = "review.completed"
command = "test {verdict} = F && notify-send roborev 'Findings in {repo_name}'"
# Run different scripts based on verdict
[[hooks]]
event = "review.completed"
command = "test {verdict} = P && ~/hooks/on-pass.sh {job_id} || ~/hooks/on-fail.sh {job_id}"

Behavior

  • Hooks run asynchronously in goroutines and never block the review pipeline
  • Hook errors are logged to the daemon log but never cause a review to fail
  • Each hook’s working directory is set to the repo path, so repo-relative commands work
  • Commands run via sh -c, so shell features (pipes, redirects, &&, ||) work
  • Hooks pick up config changes via hot-reload — no daemon restart needed
  • Multiple hooks can match the same event; they all fire independently

Debugging

Hook output and errors appear in the daemon log. To watch hooks fire in real time:

Terminal window
# Follow the daemon log (the daemon writes to stderr)
roborev daemon run 2>&1 | grep -i hook

If a hook isn’t firing, check:

  1. The event field matches the event type (use review.* to catch everything)
  2. The config file is valid TOML (cat ~/.roborev/config.toml | toml-lint or similar)
  3. The command works when run manually from the repo directory
  4. The daemon has reloaded the config (check roborev status for the config reload counter)