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.
The commands, most of which you type yourself:
| Command | What it does |
|---|---|
coduck init | Configure a repository. Once, from its root |
coduck auth | Connect coduck to Notion, or to Jira and Confluence |
coduck review | Review your own work, or a branch you have not checked out |
coduck review-pull-request | Review an open GitHub pull request against its own base |
coduck post | Put findings from a finished review on the pull request |
coduck gate | Run a gate. The coding agent calls this, not you. A stub today |
coduck completion | Print a shell completion script for bash, zsh, fish or PowerShell |
Every command takes --help: coduck review --help lists all of its flags. The full list is also in the Command reference. --verbose (-v) is on every command. It traces each step on stderr, and
in a review it also prints the suggested fix under every finding.
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.
npm install -g coduck-cli
coduck --version
Five packages, not one. Four carry a binary each and declare the platform they are for with
os and cpu, named coduck-<os>-<arch>;
coduck-cli holds a launcher and lists those four as optional dependencies. npm
installs the one that matches your machine and skips the rest, so you download about 5 MB
rather than the 21 MB all four weigh together.
Both names are unscoped, because the coduck organisation on npm is already taken.
postinstall script, and nothing is downloaded after the install. A postinstall
that fetches a binary fails closed under npm ci --ignore-scripts, which is ordinary
CI policy, and fails outright on an air-gapped machine. The binary is in the package.
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.shEither way, check it landed:
coduck --versionFrom anywhere inside the repository you want coduck to watch:
coduck init
It finds the repository root itself, by walking up to the nearest .git, so running it
from a subdirectory configures the repository rather than leaving a stray .coduck/
somewhere below it.
Three questions, in this order:
claude or codex. Recorded, not detected, see Which coding agent you run.notion, atlassian or none. Picking one opens your browser straight away, see Connect your tickets. Default is none.
Every answer also has a flag, so a scripted run skips the prompts entirely. A non-interactive run
with no flags declares claude, connects nothing and records no documents.
coduck init --agent claude --ticketing-system notion --instructions AGENTS.md| Flag | What it does |
|---|---|
--root | Repository root (default: the enclosing git repository, else the working directory) |
--agent | Coding agent you run: claude or codex |
--ticketing-system | Where your tickets live, connected during init: notion, atlassian or none |
--instructions | A document reviews should be judged against. Repeatable |
--disable-gate | Turn a gate off. Repeatable: plan, dependency, completion |
--force | Replace existing settings |
Running init where settings already exist fails rather than overwriting. --force
replaces them, and leaves your .coduck/.gitignore and every ephemeral directory
alone.
The product reviewer judges a change against what was asked for. It needs to read the ticket or the requirements page, and it reads it from the system that holds it, as you.
Coduck connects to two systems today. One connection covers Jira and Confluence, because they are one Atlassian site.
| System | Command | Covers |
|---|---|---|
| Notion | coduck auth notion | Notion pages |
| Atlassian | coduck auth atlassian | Jira issues and Confluence pages |
Both use the same door: your browser, the system's own consent screen, and nothing to set up first. No integration to create, no app to register, no API token to paste, and no secret anywhere. Coduck asks the authorization server for an identity at the moment you connect, as a public client, and proves the exchange with a one-time verifier it generated itself. That is what lets a local binary do this with no back end of ours.
Coduck asks for no scopes. It reads what you can already see, or what you share with it, and the system decides what that means. It never creates a page, edits one, transitions an issue or comments on anything.
coduck init asks where your tickets live. Answer notion or
atlassian and your browser opens there and then, while you are still at the keyboard.
coduck init
? Which coding agent do you use? claude
? Where do your tickets live? atlassian
Opening your browser to connect coduck to Jira and Confluence...To answer it from a script, or to skip it:
coduck init --ticketing-system atlassian
coduck init --ticketing-system none # do not ask, do not connectConnecting is never fatal to init. A browser that does not open, a consent screen you close, a network that is down: init has already written its settings, and the run ends with a warning naming the command to try later.
warning: could not connect to notion (...);
run `coduck auth notion` when you are readytickets notion and moves on. To add the second
system, name it: coduck auth atlassian.
Nothing about connecting depends on init, and you do not have to be in a configured repository, or in a repository at all.
coduck auth # asks which system, then connects it
coduck auth notion # connect Notion
coduck auth atlassian # connect Jira and Confluence
coduck auth with no subcommand asks the same question init asks, for somebody who
skipped it then or who wants a second system. In a script, where there is nobody to ask, it prints
its help instead.
Connecting again when you already are says so rather than opening a browser:
coduck auth notion
coduck is already connected to Notion. Use --forget to disconnect.To disconnect, both ends:
coduck auth notion --forgetThat drops what coduck holds on this machine. Remove coduck from the other end too, in your Notion settings or your Atlassian account settings, which is the half that actually revokes access.
Pass a page to a review and coduck connects on the spot if it has to. The browser opens at the start of the run, before the slow part, rather than forty seconds in when nobody is watching the terminal any more.
coduck review --base main --ticket https://acme.atlassian.net/browse/ENG-123Fetching itself happens later, when the product reviewer is about to be given the page. Splitting the two is deliberate: consent is the half that needs a person, and it should be asked for while one is there.
An address from a system coduck cannot fetch is reported the instant the command starts, before anything is scanned, connected or spent. A fetch that fails afterwards, because the page was not shared or the address is wrong, leaves the product reviewer out of the report carrying the reason. The other reviewers run exactly as they would have.
Coduck recognises addresses on notion.so, notion.site and notion.com.
Connecting is not enough on its own. Notion decides what a connection can see through its own sharing, so each page has to be handed over:
Then pass it:
coduck review --base main \
--requirements https://www.notion.so/acme/Checkout-rewrite-1a2b3c
One connection, one consent screen, both products. Coduck recognises addresses on
atlassian.net, atlassian.com and jira.com.
There is nothing to share. Coduck reads the issues and pages you can already see.
Addresses it understands, which are the ones people actually paste. Below your own site, so
https://acme.atlassian.net and then:
| Shape | Example |
|---|---|
| A Jira issue | /browse/ENG-123 |
| An issue selected on a board | /jira/software/projects/ENG/boards/7?selectedIssue=ENG-123 |
| A Confluence page | /wiki/spaces/ENG/pages/98765/Checkout+rewrite |
| A Confluence short link | /wiki/x/AbCdEf |
Whether an address is an issue or a page is decided from its path, and the reading tool is chosen to match. Coduck asks Atlassian's server what it offers and picks a tool that reads one thing: of the thirty-odd it exposes, the ones that create an issue, edit it, transition it or comment on it are never eligible, and a test enforces that.
In your home directory, one file per system, readable by you and nobody else:
~/.coduck/credentials/notion.json # 0600
~/.coduck/credentials/atlassian.json # 0600
~/.coduck/credentials/ # 0700
Nothing lands in the repository, and no token is ever written into .coduck/. The
directory is owner-only as well as the files, because a listable credentials directory tells other
users on the machine which services you have connected.
The access token expires hourly and is renewed without anybody present. If a renewal fails, because access was revoked or the refresh token itself aged out, the stored connection is dropped rather than retried on every run, and the next command that needs it asks for consent again.
Set CODUCK_HOME to keep connections somewhere else. That is the variable, not
HOME.
Coduck never opens a browser where there is no terminal to open one from. A hook, a pipeline or anything with stdin redirected gets an error naming the command to run instead:
coduck is not connected to Notion, and cannot open a browser here;
run `coduck auth notion` first
A connection is per user and per machine, so connecting once from a terminal covers every checkout
on that machine, including ones that have never run coduck init. A fresh CI runner
has none, and there is no way to hand it one today: run those reviews without --ticket or --requirements; every reviewer still runs, and
product judges the change against your documents instead of the ticket.
Three 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.
A review reports five reviewers, always the same five, so that two runs are comparable and a missing one is visible rather than absent:
| Reviewer | The question it answers |
|---|---|
product | What does this change decide for the user? With --ticket, also: did it do what was asked? |
architecture | Does it fit how this system is built? |
security | Can it be abused? |
correctness | Does it work, on the inputs it will actually see? |
dry | Does the repository already have this? Did changing shared code break callers who did not ask for it? |
--budget-dollars and --timeout cap one call each; --reviewers narrows the run.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 |
Without --target that means everything: commits on this branch, staged changes,
unstaged edits, and untracked files git is not already ignoring.
Reviewing a ref needs a working tree at that ref. Checking it out in yours would carry your
uncommitted changes across, so your scratch file ends up reviewed as part of someone else's
change. Coduck clones instead. 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. If you point --target at the branch you are standing on while
your tree is dirty, coduck says how many changes it is leaving out rather than letting you
discover it from the report.
--target is resolved in your repository before anything is cloned. A colleague's
branch usually arrives as a remote-tracking ref, so it is origin/theirs rather than
theirs.
--base is required. Git does not record which branch a branch was cut from, so
guessing it is unreliable: coduck used to try origin/HEAD, then main,
then master, and on a branch cut from another branch that once turned a ten-file
change into a 176-file review.
coduck review --base main
coduck review --base origin/develop # a base you have not checked out
git log --oneline --graph --decorate -20 # not sure what you cut from?Some comparisons cannot tell you anything however well the review runs, and coduck declines them before spending a minute and real money. There are two.
The base already contains the 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.
The change is too large to read. Measured on the hand-written part alone, so a pull request that regenerates an API client is judged on the code somebody wrote:
| Measure | Most one review can read |
|---|---|
| Hand-written diff | 1000 KB |
| Files a person wrote | 225 |
| Lines a person wrote | 20,000 |
Every limit that was passed is named, not the first, because somebody told only about files will split a change and then hit the line limit. This refuses the whole review, the secret scan included: a report listing what it managed to look at reads as a report on the change.
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 -20Everything 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.
Pass the ticket's address and coduck fetches it, as you, from the system that holds it. Every reviewer gets it; the product reviewer judges the change against it.
# a Jira issue
coduck review --base main --ticket https://acme.atlassian.net/browse/ENG-482
# a Notion page
coduck review --base main --ticket https://www.notion.so/acme/Checkout-rewrite-1a2b3c
# a Confluence page holding the spec, as well as the ticket
coduck review --base main \
--ticket https://acme.atlassian.net/browse/ENG-482 \
--requirements https://acme.atlassian.net/wiki/spaces/ENG/pages/98765
# a pull request takes the same two flags
coduck review-pull-request --url https://github.com/owner/repository/pull/1245 \
--ticket https://acme.atlassian.net/browse/ENG-482| Flag | What it takes |
|---|---|
--ticket | The ticket's address, fetched. A bare identifier such as ENG-123 is accepted too, but only labels the report; nothing is fetched |
--requirements | The page holding what was asked for, such as a spec or PRD, fetched |
Addresses on notion.so, notion.site, notion.com,
atlassian.net, atlassian.com and jira.com are fetched.
Anything else is refused as soon as the command starts, before any time or money is spent.
Connect the system first with coduck auth, or let the review open the browser the
first time. See Ticketing systems.
Without a ticket the product reviewer still runs, reviewing the change against your repository’s own documents and against itself, and asking the author what it cannot answer. With a ticket it can also say whether the change did what was asked, missed part of it, or did more.
coduck review-pull-request --url https://github.com/owner/repository/pull/1245
Pass the full URL, not just the number. A bare 1245 would be resolved against
whichever repository you happen to be in, and in an org with a frontend and a backend that
reviews the wrong pull request while looking like it worked. If you do pass a number, coduck
refuses and builds the address from your origin so you can paste it back.
You do not need to check anything out. Coduck clones into a temporary directory, reviews the pull request there, and deletes it afterwards. Your branch and your uncommitted work are untouched, so this works mid-change.
The base comes from the pull request itself. GitHub publishes pull requests as refs, and the
first parent of refs/pull/N/merge is the base tip, so this needs no GitHub token,
no login and no gh: just the access that cloned the repository.
Nothing is downloaded for the clone. The objects are already in your .git and
git clone --local hardlinks them, so only the working files are new. On a real
repository that took 0.27s. The clone and the two refs/coduck/ refs are removed
whether the review succeeded or not.
Settings come from your repository, not the clone: which agent you run, and the documents reviews are judged against. The clone supplies code and nothing else. The report is labelled with the pull request number.
coduck review --target.One line at the top, then a row per reviewer, then the findings. The headline is a code review's words, because that is what a pull request comment will have to say:
| Verdict | When |
|---|---|
REQUEST CHANGES | Something must be fixed |
COMMENT | Nothing must be fixed, and there is something worth saying |
APPROVED | Nothing found |
NOT REVIEWED | Every model call failed, so nothing below was read by a model |
Findings sort into must fix, should fix and nits, and the headline turns on the first of those alone, so it and the table can never disagree. A reviewer that did not run draws a dash, never a zero, which would read as having looked and found nothing.
Work no requirement asked for is reported beside the findings rather than among them, and does not move the verdict. Engineers improve code they are already touching, and a review that reports every unasked-for line as a defect is one people learn to skim.
--verbose adds the suggested fix under every finding. The trace it also turns on goes
to stderr, so coduck review | cat still gives the report alone.
Both review commands take all of these.
| Flag | Default | What it does |
|---|---|---|
--target | your working tree | coduck review only: the ref to review, cloned |
--base | required | coduck review only: what the change is measured against |
--url | required | coduck review-pull-request only: the pull request's address |
--reviewers | all five | Run some reviewers only: --reviewers security,correctness. The secret scan always runs |
--ticket | none | The ticket this change belongs to, fetched as you |
--requirements | none | The page holding what was asked for, fetched as you |
--model | your agent's own | Model to review with, passed to your coding agent |
--budget-dollars | 2.50 | Most one reviewer's call may spend |
--timeout | 5m | How long one reviewer's call may run |
--verbose, -v | off | Trace every step on stderr, and print the fix under every finding |
The repository is the one you are standing in; there is no --root on a review.
Reviewers run four at a time and that is not adjustable.
Coduck never posts on its own. When a pull request review finishes in a terminal it asks, and only the findings you pick are sent.
coduck review-pull-request --url https://github.com/owner/repository/pull/1245
...the report...
? What would you like to do next?
> Post comments to GitHub
Exit
? Which findings? [must fix / should fix / nits / all], or numbers like 3,4,9-11Applying fixes is not built yet, so it is not offered as a third option.
Declining costs nothing. The review is saved with the pull request it was of, and
coduck post sends it without paying for a second review.
coduck post --url https://github.com/owner/repository/pull/1245| Flag | What it does |
|---|---|
--url | required. The pull request to post to, as its full address |
--run | An earlier run to post, named as it appears under .coduck/runs |
The most recent review of that pull request is the one used. Coduck keeps the last twenty runs in a repository, so a review from last month is probably gone and posting it means reviewing again.
coduck post reviews nothing and calls no model. It reads a review that already ran and sends it.
Coduck holds no GitHub credential of its own. It posts with the access you already have, asking
gh first, then git’s credential helper.
gh auth login # or clone over HTTPS so git stores oneIf the machine has no GitHub access, the menu is not shown at all. Coduck says so once, and the report is on screen either way.
A comment has to attach to a line the pull request touched. Findings pointing anywhere else are listed on screen instead, so nothing is dropped silently.
If the fix is a single edit covering exactly the lines flagged, the comment carries a GitHub
suggestion block you can apply in one click. If it spans other lines, the fix is
written out underneath instead, with the lines it replaces, because applying it needs judgement.
Every comment carries a fingerprint. Before posting, coduck reads what it has already said on that pull request and skips anything already there, so running it again, from another machine or as another person, will not duplicate comments.
An agent is a poor judge of its own work. It has the whole conversation in context, including every reason it talked itself into the approach, so asking it whether the change is good gets you the same reasoning back.
Coduck is a different reader. It runs the agent again with a fresh context, restricted, with no access to your conversation, given only the diff, the ticket and your team’s documents. It is the same model looking at the work cold, which is what a second pair of eyes actually is.
Claude writes the change
→ coduck review --base main --ticket <url>
→ Claude fixes what came back
→ coduck review --base main --ticket <url>
→ open the pull requestThe review reads your working tree, so it runs before anything is committed. Claude can fix a finding and have it reviewed again without a branch, a push, or a pull request existing yet.
The version worth setting up first. Claude invokes it itself when it is finishing a change, because the description tells it when to.
# .claude/skills/review-with-coduck/SKILL.md
---
name: review-with-coduck
description: Use before telling the user a change is finished, and before
opening a pull request. Runs an independent review of the working tree.
---
Run the review against the base this branch was cut from:
coduck review --base main --ticket <the ticket url, if there is one>
It takes a few minutes and calls the model several times. Wait for it.
Then:
- Fix everything under MUST FIX before saying the change is done.
- For SHOULD FIX, either fix it or tell the user why you did not.
- Ignore nothing silently.
- If the product reviewer says the change does not match the ticket,
stop and ask the user rather than arguing with the finding.
Re-run the review after fixing. A finding that survives a fix usually
means the fix was wrong, not that the finding was.For running it yourself rather than leaving it to Claude:
# .claude/commands/review.md
Run `coduck review --base main $ARGUMENTS`, wait for it to finish, then
summarise what must be fixed and fix it./review --ticket https://acme.atlassian.net/browse/ENG-482
You can run a review from a Stop hook in
.claude/settings.json, so it fires whenever Claude finishes a turn.
Stop hook fires on every turn, including the ones where Claude answered
a question and changed nothing. A skill that Claude invokes when a change is actually finished
costs a fraction of it.
Without --ticket or --requirements, the product reviewer can only judge the
change against itself and your documents. With one, it answers the question you most want
answered: does this change do what was asked for. Pass the address, not a summary, and coduck fetches it as you. See
Ticketing systems.
This is also what catches the case an agent is most prone to: code that works, reads well, and implements something adjacent to the ticket rather than the ticket.
Two things in a review push back on size, and both are worth knowing about.
If you want Claude to keep pull requests small, the useful instruction is in your
CLAUDE.md rather than in the review: tell it to stop and ask before doing work the
ticket did not ask for. Coduck tells you when that happened; it cannot stop it happening.
Init writes inside .coduck/ and nowhere else. It touches no file of yours.
In the repository, committed:
.coduck/config.yaml, the coding agent, the documents reviews are judged against, and gate toggles. No secrets, ever: a connection lives in your home directory..coduck/.gitignore, keeps the ephemeral and personal files out of git, even under git add -A. Written once and never rewritten, so a line your team adds to it survives coduck init --force.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/ and .coduck/decisions/, made by init..coduck/runs/, made by the first review. The last 20 runs are kept and older ones are removed as each review finishes.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.
Everything .coduck/config.yaml accepts. config.local.yaml takes the same
keys. An unknown key is a parse error rather than a line that quietly does nothing.
# .coduck/config.yaml
version: 2
coding-agent: claude
instructions:
all: [AGENTS.md]
product: [docs/product/glossary.md]
architecture: [.claude/skills/architecture.md, docs/adr/*.md]
security: [.claude/skills/security.md]
correctness: [docs/testing.md]
dry: [docs/shared-modules.md]
gates:
plan: { enabled: true, on_error: fail_open }
dependency: { enabled: true, on_error: fail_closed }
completion: { enabled: true, on_error: fail_open }
generated_paths: [dist/, proto/**, "*.min.js"]
reviewed_paths: [api/openapi.yaml]| Key | What it does |
|---|---|
coding-agent | The CLI coduck invokes: claude or codex |
instructions | Documents to judge against, per reviewer plus all. See below |
gates | Per-gate enabled, and on_error: fail_open or fail_closed |
generated_paths | Files a tool wrote, beyond the ones coduck already recognises. Not reviewed, and not counted towards the size a review can hold |
reviewed_paths | Files that must be reviewed whatever else says otherwise. Beats every other rule, including coduck's own |
generated_paths takes three forms: dist/ for a directory at any depth,
proto/** for everything under a prefix, *.min.js for a glob.
reviewed_paths exists because coduck's shipped patterns are a guess about somebody
else's repository: a team that hand-writes an API specification coduck would skip needs a way to
say so that nothing overrules.
domain and domains are still accepted so that files written by an older coduck still parse. Nothing writes them and nothing reads them. See Domains and frameworks.coduck init asks which agent you use, claude or codex, and records it:
coding-agent: claudeThis is what coduck invokes for every reviewer, for the domain classification, and for anything else that needs a model. The calls run headless and read-only against your own logged-in session, so they count against whatever that session already counts against.
.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]
correctness: [docs/testing.md]
Everything under all goes to every reviewer, everything under a reviewer's name goes
to that one. The five keys are product, architecture,
security, correctness and dry; a misspelled one is a parse error rather than a
line that does nothing.
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 does not stop a review, because
settings outlive the files they point at, but it is named on stderr, so a review judged against
less than you believe is visible rather than silent.
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. Init warns at the moment you tick past it.
coduck init offers what it can find, up to 40 entries, with each one's file count and
size beside it so a one-page standard is distinguishable from a documentation site. It never opens
a document to decide: judging which of a team's standards matter belongs to the person reading the
list. Anything it left out is counted in the question, so a short list does not look complete.
You cannot switch a reviewer off permanently or replace how coduck reviews one. --reviewers narrows a single run.
Backend security and frontend security are different documents with different content, so coduck has to know which kind of work a change touched before it can choose what to load.
That is worked out per review, from the paths in the change, by asking your coding agent. It used
to be a prefix map typed at init, and a directory routinely holds both kinds of code:
packages/ui is React and packages/prisma is a schema;
apps/web/pages/api/*.ts is a server route and apps/web/components/*.tsx
is a component. The map got seven of fifty-two wrong.
One extra call, one turn, cents against a review costing dollars. It degrades rather than fails: if there is no agent to ask, or the answer comes back unusable, coduck loads every domain, which is broad and visible rather than narrow and wrong.
Two domains ship today, backend and frontend. Within one, coduck also
detects the frameworks and APIs in play from the repository's own manifests, and loads the
instructions for those. Infrastructure and data-science were removed rather than shipped
unmeasured: a reviewer whose quality is unknown is worse than one that is absent, because the
report gives it the same row and the same weight as the ones that were tested.
The review itself is agentic: every judgement about your code is made by a model reading the change. The one exception is a scan for credentials, which runs on every review before any model is called.
It is a scan rather than a reviewer because a leaked key is a pattern, not a judgement. It costs
nothing, takes about a second, and its findings report under security alongside
everything the security reviewer found.
| Looks for | How |
|---|---|
| Credentials in the change | Content of every changed file |
| Private keys and certificates | PEM blocks, and files such as .pem |
| Files that carry secrets by convention | Filename, for example .env and .env.production |
The last one matches on the name alone and never on content, because .env.production
may hold nothing today and everything tomorrow.
This cannot be switched off and --reviewers does not narrow it. A credential it finds
carries the same weight in the verdict as anything a reviewer reports.
Three gates, each against a coding agent event:
| Gate | Event | On error |
|---|---|---|
plan | UserPromptSubmit | fail open |
dependency | PreToolUse, scoped to Bash | fail closed |
completion | Stop and SubagentStop | fail open |
coduck gate is a stub: it appends a line to
.coduck/gate.log, prints one line, and always exits 0 so the agent proceeds. No gate
logic exists, and coduck init registers no hooks with your coding agent. The
toggles below are recorded so that the work has something to read.
coduck init --disable-gate plan writes enabled: false for that gate, and
an unrecognised name is reported rather than ignored, so nobody believes a gate is off when it is
still on.
To try the wiring by hand, the hook command is the bare word coduck:
echo '{"hook_event_name":"Stop"}' | coduck gate completion
The bare word, resolved from PATH, rather than an absolute path that is right on your machine and
breaks a teammate's checkout. Which means: if gates ever do not 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. install.sh places coduck in a directory that is
already on PATH; go install uses $(go env GOPATH)/bin, which is not.
Every command explains itself. Add --help to any of them for its full description,
examples and flags. This section is the same list, checked against the CLI.
coduck --help
coduck review --help
coduck auth notion --helpThese work on every command:
| Flag | What it does |
|---|---|
-h, --help | Show the command’s description, examples and flags |
-v, --verbose | Trace every step on stderr, and print the fix under every finding |
--version | Print the version (root command only) |
coduck init [flags]| Flag | What it does |
|---|---|
--agent | Coding agent you run: claude, codex |
--ticketing-system | Where your tickets live, connected during init: notion, atlassian or none |
--instructions | A document reviews should be judged against. Repeatable: --instructions AGENTS.md |
--disable-gate | Gate to turn off. Repeatable: plan, dependency, completion |
--force | Replace existing settings |
--root | Repository root. Default: the enclosing git repository, else the working directory |
coduck auth
coduck auth notion [--forget]
coduck auth atlassian [--forget]coduck auth on its own asks which system to connect.
| Flag | What it does |
|---|---|
--forget | On notion or atlassian: drop the stored connection instead of making one |
coduck review --base <ref> [flags]| Flag | What it does |
|---|---|
--base | Required. What the change is measured against |
--target | Branch, tag or commit to review, in a temporary clone. Default: your working tree, including uncommitted work |
--reviewers | Reviewers to run, comma separated or repeated. Default: all five: product, architecture, security, correctness, dry. The secret scan always runs |
--ticket | Link to the ticket in Notion, Jira or Confluence, fetched as you. Or a bare identifier such as ENG-123, which only labels the report |
--requirements | Link to what was asked for in Notion, Jira or Confluence, fetched as you |
--model | Model to review with, passed to your coding agent. Default: whatever it is set to use |
--budget-dollars | Most one reviewer’s call may spend. Default 2.5 |
--timeout | How long one reviewer’s call may run. Default 5m |
coduck review-pull-request --url <pull request url> [flags]| Flag | What it does |
|---|---|
--url | Required. The pull request’s full address |
--reviewers | Reviewers to run, comma separated or repeated. Default: all five: product, architecture, security, correctness, dry. The secret scan always runs |
--ticket | Link to the ticket in Notion, Jira or Confluence, fetched as you. Or a bare identifier such as ENG-123, which only labels the report |
--requirements | Link to what was asked for in Notion, Jira or Confluence, fetched as you |
--model | Model to review with, passed to your coding agent. Default: whatever it is set to use |
--budget-dollars | Most one reviewer’s call may spend. Default 2.5 |
--timeout | How long one reviewer’s call may run. Default 5m |
coduck post --url <pull request url> [--run <id>]| Flag | What it does |
|---|---|
--url | Required. The pull request to post to, as its full address |
--run | An earlier run to post, named as it is under .coduck/runs. Default: the most recent review of that pull request |
coduck gate <name> [--root <path>]Called by your coding agent, not by you. A stub today; see Gates and hooks.
| Flag | What it does |
|---|---|
--root | Repository root. Default: the repository containing the working directory |
coduck completion bash
coduck completion zsh
coduck completion fish
coduck completion powershellPrints a shell completion script, so Tab completes commands and flags. Run coduck completion zsh --help for where to put it.
make test # go test ./... -race
make lint # go vet, gofmt check, goreleaser check
make build # bin/coduck
make snapshot # cross-compile all four platforms into dist/, publish nothing
make test-install # exercise install.sh against dist/
make npm # build the five npm packages into npm/build/, publish nothing
make test-npm # package from a fake dist/, no goreleaser run needed
CODUCK_HOME relocates where connections are kept, which is what keeps a test suite
from deleting the connection of whoever ran it.