revudocs
Run modes

Direct mode

revud --direct talks to real GitHub as the gh user, with drafts and snapshots in a local SQLite store.

Direct mode runs revu against a GitHub repository as the real authenticated user. No shared bot identity is involved: every review comment, reply, and thread resolution posts to GitHub under your own account. It is the general-purpose, local-first tool — the same offline-snapshot review pipeline as the shared-identity deployment, without the multi-tenancy machinery.

Starting the daemon

From inside a clone of the repository you want to review, with Bun installed and the gh CLI authenticated:

bun run revud --direct

The --direct flag and REVU_MODE=direct are equivalent:

REVU_MODE=direct bun run revud

The daemon reads the target repository from the origin remote (git remote get-url origin), resolves and validates a GitHub token, and starts an HTTP server on port 4780. Open the URL it prints to begin reviewing.

Override the port with REVU_PORT:

REVU_MODE=direct REVU_PORT=5000 bun run revud

If the origin remote is not the repository you want to review, pass --repo owner/name or set REVU_REPO. The flag wins over the env var; a trailing .git is tolerated and stripped:

bun run revud --direct --repo octocat/hello-world
# or
REVU_MODE=direct REVU_REPO=octocat/hello-world bun run revud

Identity: authenticated as you

The token comes from the gh credential chain, resolved in this order — the first source that produces a non-empty token wins:

  1. GH_TOKEN environment variable.
  2. GITHUB_TOKEN environment variable.
  3. gh auth token — the credential stored by the GitHub CLI.

If none of these produce a token, startup fails with a clear message. Authenticate the CLI first if you haven't:

gh auth login

The token needs repo scope (or the fine-grained equivalent: Contents read, Pull requests read/write, Metadata read). The review daemon itself never pushes commits or refs, so for reviewing alone it needs only Contents: read. The token is never logged and never sent to the browser.

Verify your auth without printing the token:

gh auth status          # shows account + token scopes; never echo the token

Your display identity is read from git config, and both fields must be set:

git config user.name
git config user.email

The lowercased email is the stable key for drafts and viewed state; the name is the display layer. If either is missing, startup fails with an actionable message. In direct mode this identity is cosmetic where GitHub is concerned — GitHub authenticates the human for real — and your email never appears in any comment body; only your real GitHub login is visible to other reviewers.

Role defaults to contractor. To start as a lead:

REVU_ROLE=lead bun run revud --direct

Drafts in local SQLite

Your review is a local draft, not a GitHub-native pending review. Drafts, snapshots, blobs, viewed state, and preferences persist to a SQLite file under ${XDG_DATA_HOME:-~/.local/share}/revu. Override the directory with REVU_DATA_DIR:

REVU_MODE=direct REVU_DATA_DIR=/tmp/my-revu-data bun run revud

A restart loses nothing: the daemon re-reads the store and every draft survives. Drafts save automatically as you type — debounced at 600ms, with a flush on tab hide. A hard crash inside the debounce window can lose up to the last 600ms of typing, and a draft is deleted from disk only on confirmed submit success.

An inline review thread open against a diff line — read, reply, and resolve inline; drafts save as you type and survive a restart

The review loop

  1. Sync — click a pull request in the list. The daemon fetches the PR's diff, files, threads, and commits from GitHub REST + GraphQL and stores them locally.
  2. Comment — open a file, select lines, and write inline comments. The draft saves automatically as you type and survives a restart.
  3. Submit — submit the draft as a COMMENT, APPROVE, or REQUEST_CHANGES review. The draft is deleted from disk only on confirmed success. A head_moved response (the PR was force-pushed between sync and submit) surfaces as a conflict and keeps the draft intact so you can re-sync and resubmit.
  4. Reply — reply to a review thread. Replies post as single inline comments on the existing thread.
  5. Resolve / unresolve — mark a review thread resolved or reopen it, via the GitHub GraphQL resolveReviewThread / unresolveReviewThread mutations.

The review bar — submit a draft as Comment, Approve, or Request changes; Approve is gated to where it actually works

Approve gating

Comment is the primary action. Approve and Request-changes are gated per-PR and are only live where they actually work — in direct mode, when the PR was not opened by you. When approval isn't available, the UI says what to do instead of graying out a dead end.

Rate budget and the snapshot

A PR is a snapshot: one sync burst, then fully-local review. Context expansion, thread reading, and file jumping all work with the network gone, because immutable content is fetched once and reused across syncs. Reviewing needs the repository cloned locally — blob reads come from your git object store, which is what keeps a large-PR sync cheap. Threads, checks, and mergeability refetch on every sync, unconditionally, so a thread resolved elsewhere with zero new commits still shows up. Only the PR list polls, and it is free on ETag 304 responses.

Checks — check runs and mergeability refetch on every sync, unconditionally, shown here with a failing check run and a log excerpt

Reviewing a branch with no pull request

Direct mode also reviews local branches. Pick a base and a head from the branches in your workspace and review the diff between them with no pull request, no push, and no token — it is the same pipeline (same snapshot, same drafts, same reconcile) pointed at a branch pair instead of a pull request, and it needs no network at all.

Start one from the inbox header's New local review control, with g l, or from the command palette's Go group.

Three things differ from a pull-request review, and each is deliberate:

  • Committed content only. The diff is the merge base of the pair against the head tip, built from commit SHAs, so uncommitted edits in your working tree are not in it. When the tree was dirty at the last sync, the review says so in a banner under its header.
  • The merge base is computed against the live base tip. git merge-base is run against wherever the base branch is right now, so a local review's compare key can change on a base advance with zero new head commits. A pull request cannot do that: GitHub only refreshes a pull request's base SHA when it records a synchronize event. The two paths genuinely disagree about what changed, and the local answer is the one taken here.
  • Nothing is ever published. The local write path holds no GitHub client at all — the capability is structurally absent from the module rather than declined by a branch inside it — so a local comment cannot reach GitHub, no publish or export action exists, and nothing in a local review is ever copied to a pull request. A local review is archived read-only, keeping everything it holds, when a sync finds an open pull request for the same branch pair in the same repository; that check is a read of GitHub that runs only on sync, and it is absent in a workspace with no remote, where a local review stays open until you delete it.

Local reviews persist to the same SQLite file as everything else, in their own tables. Full walkthrough: Reviewing a local branch.

Environment variables

Direct mode's environment variables:

VariableDefaultPurpose
REVU_MODEmockmock, direct, or broker (reserved).
REVU_PORT4780HTTP server port.
REVU_REPOorigin remoteRepository override (owner/name); a --repo flag wins over it.
REVU_DATA_DIR~/.local/share/revuDurable SQLite store (drafts, snapshots, blobs, viewed state).
REVU_ROLEcontractorSession role; lead to start as a lead.
REVU_LOCAL_ONLY01/true drops the requirement for a GitHub repository, so the daemon boots with no origin, no token and no viewer, serving local reviews alone; 0/false keeps the default. --local-only is the equivalent flag.
REVU_DIST_DIRpackages/app/distDirectory of the built frontend the daemon serves.
GH_TOKEN—Token override, ahead of GITHUB_TOKEN and gh auth token.
GITHUB_TOKEN—Token override, ahead of gh auth token.

REVU_LOCAL_ONLY takes 1/true or 0/false and nothing else: any other value is refused at startup rather than quietly read as off. The switch also belongs to direct mode alone — set it with mock or broker resolved and the daemon refuses to start rather than ignore it. And it lifts a requirement rather than cutting a connection: in a repository that does have an origin, a local-only daemon still resolves that origin and probes GitHub as usual, and serves its pull requests beside local reviews.

The full check gate

Before any change is ready, run the local gate — lint, typecheck, bun test, and the app build, in order. All four must pass:

bun run check

Where to go next

On this page