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 --directThe --direct flag and REVU_MODE=direct are equivalent:
REVU_MODE=direct bun run revudThe 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 revudIf 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 revudIdentity: 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:
GH_TOKENenvironment variable.GITHUB_TOKENenvironment variable.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 loginThe 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 tokenYour display identity is read from git config, and both fields must be set:
git config user.name
git config user.emailThe 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 --directDrafts 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 revudA 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.

The review loop
- 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.
- Comment — open a file, select lines, and write inline comments. The draft saves automatically as you type and survives a restart.
- Submit — submit the draft as a
COMMENT,APPROVE, orREQUEST_CHANGESreview. The draft is deleted from disk only on confirmed success. Ahead_movedresponse (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. - Reply — reply to a review thread. Replies post as single inline comments on the existing thread.
- Resolve / unresolve — mark a review thread resolved or reopen it, via the
GitHub GraphQL
resolveReviewThread/unresolveReviewThreadmutations.

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.

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-baseis 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 asynchronizeevent. 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:
| Variable | Default | Purpose |
|---|---|---|
REVU_MODE | mock | mock, direct, or broker (reserved). |
REVU_PORT | 4780 | HTTP server port. |
REVU_REPO | origin remote | Repository override (owner/name); a --repo flag wins over it. |
REVU_DATA_DIR | ~/.local/share/revu | Durable SQLite store (drafts, snapshots, blobs, viewed state). |
REVU_ROLE | contractor | Session role; lead to start as a lead. |
REVU_LOCAL_ONLY | 0 | 1/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_DIR | packages/app/dist | Directory 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 checkWhere to go next
- Mock mode — the in-browser demo and test oracle.
- Broker mode — the reserved shared-identity design.
- Run modes overview — how the mode is selected and what the three modes share.