Everything coduck does today, in the order you'll actually need it.
Coduck reviews what your coding agent is about to do, before it does it.
Coduck drives the coding agent you already run. It does not ship a model and it does not talk to a service of ours.
claude or codex on your PATH. Whichever you use, logged in as normal. Coduck calls it the way you would.git, and a repository with a branch to review.Nothing else. There is no account to create and no key to paste.
cd <the folder you unzipped this into>
sh ./install.sh
It picks the archive for your machine, checks it against checksums.txt, and refuses
to install if they disagree.
It installs into the first conventional location that is both writable and already on your PATH,
/usr/local/bin, Homebrew's bin, ~/.local/bin or ~/bin. If
none of them qualifies it falls back to ~/.local/bin and prints the exact
export PATH=... line to add, for the shell you run.
To choose the location yourself:
CODUCK_INSTALL_DIR=~/bin sh ./install.shCheck it landed:
coduck --version # 0.1.0From the root of the repository you want coduck to watch:
coduck initIt asks two things, which coding agent you run, and what kind of work lives here, then offers to point reviews at documents you already keep. Everything has a flag, so a scripted run skips the prompts.
coduck init --agent claude --domain backend --instructions AGENTS.md| Flag | What it does |
|---|---|
--root | Repository root (default: the enclosing git repository) |
--agent | Coding agent you run: claude or codex |
--domain | Kind of work: backend, frontend, infrastructure, data-science. One per path, in order |
--instructions | A document reviews should be judged against. Repeatable |
--disable-gate | Turn a gate off. Repeatable: plan, dependency, completion |
--force | Replace existing settings |
Name paths to answer separately for each, which is what a monorepo wants:
coduck init ./api ./webThree jobs, two commands.
coduck review --base main # your own work
coduck review --target origin/theirs --base main # a colleague's branch
coduck review-pull-request --url <github pull request url>Everything is a flag. Nothing is positional, so no argument depends on where it sits and none means two different things in two commands.
What --target does decides which:
| Reviewed | Uncommitted work | Where | |
|---|---|---|---|
| without --target | your working tree | included | in place |
| with --target | that ref | excluded, a ref has none | a temporary clone |
Cloning is not a preference. Reviewing a ref needs a working tree at that ref, and making yours
hold it means a checkout, and git carries uncommitted changes across a checkout whenever they do
not conflict, which quietly reviews your scratch file as part of somebody else's change. Nothing
is downloaded, the objects are already in your .git and git hardlinks them.
Your branch and your own work are never touched, so reviewing a colleague's branch works in the middle of your own.
It used to be guessed, origin/HEAD, then main, then master,
and on a branch cut from another branch that reached back past everything already merged there, a
ten-file change was reviewed as 176 files. Git does not record which branch a branch was cut from,
so naming it is the only way to be sure.
coduck review-pull-request --url https://github.com/owner/repository/pull/1245
A bare 1245 is a pull request in some repository, and coduck would resolve it against
whichever one you happen to be standing in. In an organisation with a frontend and a backend, that
reviews a real pull request that is not the one you meant, and the report looks exactly like a
correct one. The URL names the repository, so a mismatch is refused, and if you pass a number
anyway, coduck builds the address from your own origin so it is one paste.
The base comes from the pull request itself, read straight from git, GitHub publishes pull
requests as refs, so this needs no GitHub login, no token and no gh.
coduck review --target <ref> --base <base>.--no-agents for the static checks alone, which takes about a second and costs nothing.Some comparisons cannot tell you anything however well the review runs, and coduck declines them before spending a minute and real money. There is one today, the base already contains every commit on your branch, and has moved on past it. Nothing of the branch can be in the diff, so a review of it says nothing about the branch, which is how a finished ticket once got reported as "entirely unimplemented".
Reviewing work you have not committed yet is not this case and is never refused. Pass
--review-anyway to review regardless.
A refusal exits non-zero, unlike a verdict. It looks like this:
coduck review
NOT REVIEWED the base already contains this branch
this branch feature/ENG-TASKS-1245-COPY no commits of its own
the base origin/feature/launchpad 59 commits ahead
Only uncommitted work could differ, so reviewing against this base
cannot tell you anything about the branch.
Review against the base this branch was cut from
git log --oneline --graph --decorate -20
Or review the uncommitted work on its own
coduck review --base origin/feature/launchpad --review-anywayEverything coduck says when it does not review goes to stderr, stdout carries a review or nothing, so a script reading it gets a report or an empty stream, never a sentence where a table should be.
Without a requirement to check the change against, the product reviewer is skipped rather than guessing the requirements from the code it is reviewing.
| Flag | What it takes |
|---|---|
--ticket | The ticket, a page address coduck fetches, or an identifier such as ENG-123 that only labels the report |
--requirements | The page holding what was asked for, fetched as you |
A ticket is fetched from the system that holds it, or it is not supplied. Pasting one in, or pointing at a file somebody exported, produced a copy that was stale the moment the ticket changed, and a review judged against a stale requirement is confidently wrong in the way that is hardest to notice.
Connect Notion once with coduck auth notion, then share the page with coduck from its ••• menu, under Connections.
The base comes from the pull request itself, read straight from git, GitHub publishes pull
requests as refs, and refs/pull/N/merge's first parent is the base tip. So this
needs no GitHub token, no login and no gh, only the access that cloned the
repository.
coduck review --target.You do not have to check anything out. Coduck clones the repository into a temporary directory, checks the pull request out there, reviews it, and removes it. Your branch and your uncommitted work are untouched, so this works in the middle of your own change.
Nothing is downloaded to do it, after the refs are fetched, the objects are already in your
.git, and git clone --local hardlinks them. Measured on a real
repository, 0.27s, with the object store shared rather than copied, only the working files are
new. The clone and the two refs/coduck/ refs are removed afterwards whether the
review succeeded or not.
Settings still come from your repository, not the clone, which domain this is, which agent you run, and the documents reviews are judged against. The clone supplies code and nothing else.
| Flag | Default | What it does |
|---|---|---|
--reviewers | all four | Run some reviewers only: --reviewers security,standards |
--target | your working tree | The ref to review, cloned |
--base | required | What the change is measured against |
--no-agents | off | Static checks only, no model calls, no spend |
--review-anyway | off | Review even when a pre-check refuses |
--root | the enclosing repository | Which repository to review |
--budget-dollars | 2.50 | Most one reviewer's call may spend |
--timeout | 5m | How long one reviewer's call may run |
--concurrency | 4 | How many reviewers to review at once |
--verbose | off | Trace every step, on stderr |
In the repository, committed:
.coduck/config.yaml, the domain, the coding agent, the documents reviews are judged against, and gate toggles. No secrets..coduck/.gitignore, keeps the ephemeral and personal files out of git.Yours, gitignored:
.coduck/config.local.yaml, overrides anything in the committed file. This is where you say you run codex while the team's file says claude, rather than committing your preference onto everybody.Ephemeral, safe to delete:
.coduck/cache/, .coduck/decisions/ and .coduck/runs/.Settings are read committed-file-first, then the local one, then flags. A layer that does not mention something does not override it, maps merge key by key and document lists are combined, so a local file can add one note without restating the team's.
coduck init asks which agent you use, claude or codex, and records it:
agent:
name: claudeCoduck will use this to invoke your agent when it needs one. Nothing invokes an agent yet, the value is recorded so that work has something to read.
codex, cursor or copilot records the choice, skips hook registration, and says so in the summary, coduck's gates will not fire automatically for those agents yet. This is a separate question from which agent is detected on disk, a repository can carry a .claude directory from a colleague's setup while you run something else, so declaring your agent is what decides, not what happens to be lying around.Coduck decides how code is reviewed. Your team supplies what to judge it against.
This matters more than it looks, reviewing agents run restricted and load nothing on their own,
not CLAUDE.md, not AGENTS.md, nothing in .claude/.
Whatever your team has written down is invisible to a review unless it is named here.
# .coduck/config.yaml
instructions:
all: [AGENTS.md]
architecture: [.claude/skills/architecture.md, docs/adr/*.md]
security: [.claude/skills/security.md]
standards: [.claude/skills/frontend-standards.md]
Everything under all goes to every reviewer, everything under a reviewer's name goes
to that one. Globs are allowed and may match nothing, a team often lists docs/adr/*.md
before writing the first one. A path named exactly and not present is an error, because somebody
wrote it believing it was being read.
Two limits worth knowing. Paths must stay inside the repository. And each reviewer is a separate model call, so the same document is charged once per reviewer, there is a size limit, and going over it names the files rather than silently truncating your standards.
You cannot switch a reviewer off permanently or replace how coduck reviews one. --reviewers narrows a single run.
Three gates, registered against four Claude Code events:
| Gate | Event |
|---|---|
plan | UserPromptSubmit |
dependency | PreToolUse, scoped to Bash |
completion | Stop and SubagentStop |
coduck init merges these into .claude/settings.local.json, leaving your
own settings and hooks alone. If no agent is detected it prints the mapping so you can wire it by
hand.
That is Claude Code's personal settings file, not the shared settings.json. The hook
command is the bare word coduck, resolved from your PATH, so a path that is right on
your machine cannot break a teammate's checkout or CI.
coduck init adds .claude/settings.local.json to your
.gitignore for you, unless a rule already covers it. It never creates a
.gitignore, if the repository has none, init says so and leaves the file for you to
make. Committing the settings file would hand every teammate a hook that runs whatever their own
PATH resolves coduck to, which for most of them is nothing.
If the gates never seem to fire, check that coduck is on the PATH of whatever launches
your agent. A GUI-launched editor does not inherit a shell's PATH on macOS, brew install
and install.sh both place coduck in a directory that is already on PATH, but
go install uses $(go env GOPATH)/bin, which is not.
make test # go test ./... -race
make lint # go vet, gofmt check
make snapshot # cross-compile all four platforms into dist/, publish nothing
make test-install # exercise install.sh against dist/
Homebrew and GitHub Releases are configured in .goreleaser.yaml but disabled.
Enabling them needs a public repo, a homebrew-tap repo, and a tag, the config itself
is already written and snapshot-tested.
Once the repo is public:
go install github.com/royihaddad/coduck/hosted/cmd/coduck@latest