From workflow-skills
Use when the user wants a collaborative review of a PR — their own read reconciled against existing bot/reviewer comments, with high-confidence fixes applied and judgment calls surfaced — typically via /co-review or asking for a "co-review". Flags — --local reviews the uncommitted working tree (no PR); --remote skips local reviewer agents; --post reviews someone else's PR and posts vetted findings to GitHub instead of editing files.
How this skill is triggered — by the user, by Claude, or both
Slash command
/workflow-skills:co-reviewThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Combines a fresh review of the PR with whatever comments are already on GitHub — plus, optionally, reviews from other local agents you have installed — then splits the result into "auto-fix" and "ask the user."
Combines a fresh review of the PR with whatever comments are already on GitHub — plus, optionally, reviews from other local agents you have installed — then splits the result into "auto-fix" and "ask the user."
The main agent writes the review and applies fixes, but does not judge whether its own findings are correct. That job is delegated to a sub-agent (the reconciler) which sees the main agent's review, any local-agent reviews, and the GitHub comments side-by-side without knowing which came from "you." This isn't perfect independence — the main agent still chooses what to flag and writes the prose the reconciler reads — but it prevents the obvious failure mode (an agent grading its own homework).
Three mode choices:
--local it operates on your working tree instead: no PR required, no GitHub calls.--remote forces them off for one run.--post it assumes you're reviewing someone else's PR: it never touches the code and instead posts the vetted findings back to GitHub as a PR review.--local — review local changes instead of a PR. Diff comes from git diff <base>: your working tree (committed and uncommitted changes) compared against <base>, plus any untracked files (git ls-files --others --exclude-standard), which git diff does not show — read those so brand-new files aren't silently skipped. No gh calls are made and no PR is required. Caveat: git diff <base> compares against <base>'s current tip, so if <base> has advanced since you branched it will also surface those upstream commits as reversed changes — diff against the merge-base instead (compute it in a separate call; don't use $(...), per step 2).--base <branch> — base to diff against in --local mode. Defaults to main.--remote — skip local agents for this run: the main agent reviews and folds in GitHub comments as usual, but gemini/codex are not probed, asked about, or dispatched, and the config is left untouched. Useful for a quick "just the normal PR review" without spinning up extra agents. Mutually exclusive with --local (which drops GitHub entirely); if both are passed, stop and ask which the user meant.--post — review someone else's PR and post the vetted findings back to the PR instead of editing local files. The review and reconciliation are identical to the default flow, but the auto-fix step is replaced: nothing in the working tree is ever changed, and high/medium findings (after you vet them) are submitted as a single GitHub PR review with inline comments. Requires a PR — mutually exclusive with --local; if both are passed, stop and ask which the user meant. Composes with --remote (post a Claude-only review) and with local reviewers (post a reconciled multi-agent review).Other local agents can act as extra reviewers. Resolution mirrors the task system's config pattern.
Config file (local): dev_docs/co-review/.co-review.yml. Treat this as local config — the setup step below adds dev_docs/co-review/ to the repo's local git exclude (.git/info/exclude) so it stays out of git status and never lands in an accidental commit, which matters most in repos you don't own. (If someone has committed a .co-review.yml to a repo you're reviewing, the untrusted-config rules below still apply.)
local_reviewers:
- gemini # known agent → built-in default invocation
- codex
- name: my-agent # custom agent → explicit invocation
command: "my-agent review --stdin"
Resolution:
PATH (command -v) for the known default agents and ask the user which (if any) to use, then write their choice to the config so it isn't asked again.local_reviewers: [] (explicit empty list) → the user chose "none." Run Claude-only and do not re-ask. (Absent ≠ empty — that distinction is what lets the skill remember and skip asking.)gemini, codex) silently and note which ran. Any custom command:, or any agent not on the built-in list, is untrusted (see the safety note below) — show the user the exact command and get explicit confirmation before running it.Detection (PATH probe + config override): the known default list is gemini and codex. Probe each with command -v. The config may also name agents that aren't on the default list, supplying a command: for how to invoke them.
Built-in invocations. Everything that varies per PR — the rubric, any reviewer-specific requests, and the diff — is assembled into one stdin stream; the prompt argument is a short fixed pointer. Because nothing variable ends up in the command string, the command is invariant and can be approved once with an exact-match rule (see Permissions).
Assemble the input and pipe it to the agent in a single shell invocation (one Bash call), so the bytes the agent reads are written and read in the same shell — and therefore the same sandbox context (see the note below for why splitting this across two calls desyncs). Build the stream with cat + redirection only — no { } group, no here-doc — so every part stays within the permission matcher's documented contract: only |, &&, ;, &, and newlines split a command into separately-matched segments, and redirection (>, >>) is transparent to matching. <INPUT> is a fixed absolute path (not a $TMPDIR-relative one); opening it with > truncates any leftover file before the diff is read, so a prior run's bytes can never be consumed.
<REQUESTS> (a fixed absolute path) with the requests, or skip it entirely when there are none. Keeping the requests in a file (not inline in the command) is what keeps the command text invariant, so the exact-match approval below still holds whatever the requests say.gemini, GitHub mode, with requests → cat "<this skill dir>/review_prompt.md" "<REQUESTS>" > "<INPUT>"; gh pr diff <n> >> "<INPUT>"; cat "<INPUT>" | gemini -p "<POINTER>"gemini, GitHub mode, no requests → drop the "<REQUESTS>" argument: cat "<this skill dir>/review_prompt.md" > "<INPUT>"; gh pr diff <n> >> "<INPUT>"; cat "<INPUT>" | gemini -p "<POINTER>"codex, GitHub mode → identical, piped to codex exec --sandbox read-only "<POINTER>" (use whatever read-only/sandbox flag your codex version supports).--local mode → swap gh pr diff <n> for git diff <base>, and append any untracked files you read in the same invocation: … ; git diff <base> >> "<INPUT>"; cat <untracked-file> … >> "<INPUT>"; cat "<INPUT>" | <agent> ….Each ;/|-separated segment is permission-matched on its own — cat … → Bash(cat:*), gh pr diff … → Bash(gh pr diff:*), git diff … → Bash(git diff:*), and the gemini/codex tail → its exact rule — and redirection (>, >>) is transparent to matching, so this assemble-then-dispatch line is fully covered by the rules below. The only things that change between runs are the contents of <REQUESTS>, <INPUT>, and the diff — all files, never the command text.
where <POINTER> is exactly:
Review the rubric and diff on stdin. Output findings as file:line, the issue, and a suggested fix. Read only: do not modify files or run commands.
A custom agent must supply its own command: (input is piped on stdin).
Why a single shell call. Splitting assembly and dispatch across two Bash calls silently feeds reviewers a stale diff: assembly runs sandboxed while the network-bound reviewer runs unsandboxed, and
$TMPDIR//tmpresolve differently across that boundary, so the read comes from a leftover file. Write and read<INPUT>in the same invocation, open it with>to truncate leftovers, and key it off a fixed absolute path — never$TMPDIR. Don't reach for a{ }group or here-doc either: they aren't in the matcher's splitter set (|,&&,;,&, newlines), so they'd void the approve-once exact-match rules.
These agents must be constrained to read-only: they should emit a review and nothing else. Agentic CLIs like codex exec can edit files or run commands by default — the pointer says read-only and the codex invocation pins a sandbox flag, but never rely on the prompt alone: keep the sandbox flag in both the command and its allow-rule, especially in --local mode where edits are in flight.
Untrusted config —
.co-review.ymlis committed to the repo under review. This skill runs in repos you don't control, so the config (and any customcommand:) can be supplied by whoever wrote the repo. Treat a customcommand:, or any agent not in the built-in list (gemini,codex), as untrusted code: never run it silently. Print the agent name and the exact command, and get explicit user confirmation before executing. Only the built-in agents invoked through their documented commands may run without a prompt.
The reviewer command is invariant: everything that varies per PR (the diff and any reviewer-specific requests) travels on stdin, and the prompt argument is a fixed pointer. So you can approve each reviewer once with an exact-match rule — no broad wildcard. Merge the rules for the reviewers you use into the permissions.allow array in ~/.claude/settings.json (user-wide) or the repo's .claude/settings.json — don't overwrite an existing settings file:
{
"permissions": {
"allow": [
"Bash(cat:*)",
"Bash(gh pr diff:*)",
"Bash(git diff:*)",
"Bash(gemini -p \"Review the rubric and diff on stdin. Output findings as file:line, the issue, and a suggested fix. Read only: do not modify files or run commands.\")",
"Bash(codex exec --sandbox read-only \"Review the rubric and diff on stdin. Output findings as file:line, the issue, and a suggested fix. Read only: do not modify files or run commands.\")"
]
}
}
Why this is narrow:
gemini / codex rules are exact — they authorize only this one read-only review command with that exact prompt. They do not grant arbitrary gemini -p or codex exec runs, and the codex rule pins --sandbox read-only into the approved string. Edit the pointer and Claude Code re-prompts, so the approval can't silently come to mean something else.Bash(cat:*), Bash(gh pr diff:*), and Bash(git diff:*) cover assembling the input stream — they only read repo/PR data; the sole write is the redirected <INPUT> temp file (redirection targets aren't constrained by the rule, and it's written and read in the same shell call). Add only the diff source you use (gh pr diff for PRs, git diff for --local).codex version uses a different read-only flag, update both.command: agents from .co-review.yml — those are untrusted by design (see above) and must stay prompt-on-every-run. (Plugins can't ship permission rules — only agent/subagentStatusLine settings — so this is a manual one-time step per user.)Parse invocation. Note any --local, --remote, --post, and --base <branch> flags and whether a PR number was passed. --local and --remote are mutually exclusive — if both are present, stop and ask which was meant. --post requires a PR and is mutually exclusive with --local — if both are present, stop and ask which was meant.
Identify the PR (skip entirely in --local mode).
git branch --show-current first, then gh pr list --head <branch> --json number,url with the literal branch value substituted in. Do not combine them with $(...) — command substitution inside a Bash tool call is rejected by the permission matcher even when both subcommands are allowlisted.--local if the user just wants to review uncommitted work).Gather inputs.
gh pr view <n> --json title,body,reviews,comments,filesgh pr diff <n>gh api repos/{owner}/{repo}/pulls/<n>/comments for inline review comments (top-level comments from gh pr view does not include inline diff comments).--local): git diff <base> (default base = main) for tracked changes, plus untracked files via git ls-files --others --exclude-standard so new files aren't missed (mind the merge-base caveat in the Flags section if <base> has advanced). No gh calls. There are no GitHub comments to reconcile.Resolve local reviewers. If --remote was passed, skip this step entirely — no probe, no prompt, no config write — and continue with no local agents. Otherwise read dev_docs/co-review/.co-review.yml:
Absent → probe PATH for the known agents and ask the user which to use, then write the choice (including an empty list if they decline all) to the config. Since this is local config, also suggest the user keep it out of git by adding the directory to the repo's local exclude (.git/info/exclude is per-clone and never committed; the git check-ignore guard keeps it idempotent):
git check-ignore -q dev_docs/co-review/ || echo 'dev_docs/co-review/' >> "$(git rev-parse --git-dir)/info/exclude"
Empty list → no local reviewers; continue Claude-only.
Entries present → built-in agents (gemini/codex) are used; for any custom command: or unknown agent, show it and get explicit confirmation first (see the untrusted-config note). Note which will run.
Dispatch local-agent reviews (if any) in parallel. Assemble the shared input stream (rubric + any reviewer-specific requests + diff) and pipe it to each agent with the fixed pointer prompt, exactly as described under Local reviewers → Built-in invocations; capture stdout. For any custom command: or non-built-in agent, show the command and get explicit user confirmation before the first run (untrusted config; see the note in Local reviewers). If an agent errors, times out, or isn't actually runnable, note it and continue — a missing reviewer is not fatal. Output is free-form prose; do not impose a JSON contract on external tools.
Assess scope first. Before any per-line review, judge whether the change is too big and should be split. Only raise this if you have high confidence — don't flag every multi-file change. Signals that justify a split call:
Mere line count or file count alone is not sufficient — a large mechanical rename is fine as one unit. If you do call a split, name the proposed pieces concretely (files/hunks + one-line description each), and present the recommendation to the user as part of the review. If the change is appropriately sized, say so. (This runs in --local mode too — useful before a PR even exists.)
Review the change yourself. Form an independent review focused on:
file:line, the issue, and your suggested fix.Spawn the reconciler sub-agent (general-purpose). Give it:
--local modeAsk the sub-agent to:
{file, line, issue, source, confidence, recommended_fix, rationale}.--post mode, tell the reconciler the findings will be posted as comments on someone else's PR, so each recommended_fix should read as a concrete suggestion addressed to the author, not as an edit you're about to make.Reconcile and present to the user. Always note which reviewers contributed (Claude + which local agents ran, or which were skipped and why). Then branch on disposition:
--post mode (someone else's PR):
file:line, the issue, the suggested fix, and its tier. These are what may be posted, pending your vetting (step 10).The remaining steps depend on disposition.
Default disposition (your PR):
Apply high-confidence fixes with Edit. Verify each:
bash -nWait for the user's answers on the medium items. Apply the ones they say yes to.
Commit and push the changes. Once the fixes are applied and verified, commit them and push to the current branch's upstream:
main/master), stop and tell the user to move the fixes onto a feature branch first — don't auto-commit or push review fixes straight to the default branch.Apply co-review fixes), summarizing the items addressed.git rev-parse --abbrev-ref --symbolic-full-name @{u} succeeds), a plain git push is enough. Otherwise set one explicitly against the branch's intended remote — git push -u <remote> HEAD, where <remote> is the configured remote (default origin, but don't assume it: fall back to whatever git remote reports if origin isn't present).--post disposition (someone else's PR):
Vet before posting. Never touch the working tree — you don't own this code. Present the numbered post-candidate list and let the user deselect, edit the wording of, or pull a low finding into any candidate. Nothing is posted until the user explicitly approves the final set. If they approve none, stop and say so — post nothing.
Choose the verdict. Ask the user which review event to submit: COMMENT (neutral), REQUEST_CHANGES, or APPROVE. Ask this every run; don't assume.
Post one batched PR review. Submit a single review via gh api repos/{owner}/{repo}/pulls/<n>/reviews (use --method POST with --input reading a JSON file you write, so quoting and newlines survive):
event = the chosen verdict.body = a short summary plus any findings that can't be anchored to a specific diff line (e.g., "missing test for X", whole-file concerns).comments = an array of {path, line, body}, one per anchored candidate. line is the actual file line number on the right/new side of the diff (not a relative diff position) — a comment on an unchanged line is rejected by the API. Before submitting, anchor-check every comment: confirm its line is among the diff's added/modified right-side lines, and fold any that don't anchor into body instead. The review POST is atomic — a single bad line rejects the whole review and posts nothing, so validate up front rather than reacting to a rejection. If the POST still fails, retry once with the offending comment(s) moved to body.Report the result. Print the review URL (gh pr view <n> --json url plus the review, or the API response's html_url). Don't commit or push anything — you changed no files.
command: or non-built-in agent from .co-review.yml — it's repo-controlled, untrusted code. Show it and confirm first.--post mode, never edit the reviewed code — it isn't yours. The only output is the GitHub review.--post mode, nothing is posted to GitHub until the user has vetted and explicitly approved the final comment set and chosen the verdict.Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.
npx claudepluginhub bestdan/workflow-skills --plugin workflow-skills