Skip to content
Back to skills

Council Review

BSecurity

Multi-vendor code review council — runs the Claude code-reviewer agent alongside external second opinions (codex, gemini) in parallel, then synthesizes with citation verification, source tagging, and disagreement surfacing. Optionally seats grok (--with-grok) and/or an OpenRouter free-tier advisor (--with-free) as non-voting advisors. NOT a completion gate (that is /verify-completion), NOT a security audit (security findings route to security-reviewer), and NOT free for the codex/gemini lanes...

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 8, 2026
ai-agentspythongoshellbashnodecode-reviewgitapibackendsecurity

Works with

  • claude code
  • cli
  • api

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned October 8, 2026

npx -y skills add joymin5655/Agent --skill council-review --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Council Review?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Council Review
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/joymin5655-council-review/badge)](https://www.skillsdirectory.com/skills/joymin5655-council-review)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: council-review
description: Multi-vendor code review council — runs the Claude code-reviewer agent alongside external second opinions (codex, gemini) in parallel, then synthesizes with citation verification, source tagging, and disagreement surfacing. Optionally seats grok (--with-grok) and/or an OpenRouter free-tier advisor (--with-free) as non-voting advisors. NOT a completion gate (that is /verify-completion), NOT a security audit (security findings route to security-reviewer), and NOT free for the codex/gemini lanes — those are paid CLI calls the user approves first (--with-free itself costs nothing but still sends the diff to a third party — see step 2).
when_to_use: User wants a code review with independent cross-vendor opinions — "council review", "get a second opinion on this diff", "review with codex/gemini", or `/council-review [--staged|--head|<range>] [--with-grok] [--with-free]` — or `council-escalation-gate.py` denied a plain code-reviewer dispatch because the diff is council-scale (line/file threshold or a risk-area path; core/infra/council-threshold.sh).
tools: Bash, Read, Grep, Glob, Agent
---

# /council-review

## Goal

One diff, several independent reviewers, one synthesized report. Different
models fail differently — a finding two vendors reach independently outranks a
finding one vendor reaches confidently, and a synthesizer that answers to the
actual code (not to the reviewers) drops what was hallucinated.

## Lanes

| Lane | Vendor | Lens | Dispatch | Participates in verdict |
|---|---|---|---|---|
| `code-reviewer` agent | anthropic | its own agent charter | Agent tool (model pin: sonnet, unchanged) | yes |
| `second-opinion-review` | openai (codex) | implementation correctness | `core/infra/call-worker.sh` | yes |
| `third-opinion-review` | google (gemini, via antigravity) | architecture & consistency | `core/infra/call-worker.sh` | yes (when lane enabled) |
| `advisor-third` (`--with-grok`) | xai (grok) | unscoped (free perspective) | `core/infra/call-worker.sh` | **no — advisory only** |
| `advisor-free` (`--with-free`) | openrouter (model vendor pinned in `adapters/openrouter/*.template` — see `core/infra/backends.json`'s honesty comment) | unscoped (free perspective) | `core/infra/call-worker.sh` | **no — advisory only** |

Lane SSOT is `core/infra/backends.json`. A disabled lane (e.g. one whose CLI
or preflight is missing) refuses loudly at dispatch; report it as absent —
never substitute another vendor for it (a fallback that shares a seated
vendor would fake the independence signal).

The grok lane runs on the free tier by design (decided 2026-08-19): when its
quota is exhausted mid-review the lane surfaces `status: rate-limited` and the
council **fails open** — the review proceeds without the advisory, the lane
status line says "rate-limited, retry later", and no upgrade prompt is put to
the user. The openrouter free lane carries the same fail-open contract on its
own 429s (`adapters/openrouter/README.md`), but its cost is retention, not
quota — see step 2.

## Steps

### 0.5. Mark council-active before dispatching the Claude lane

Before this skill's own `code-reviewer` Agent dispatch (step 3), set the
council-active flag through the gate's own CLI — never write a flag file
directly. The escape hatch now lives OUTSIDE this repo (keyed to the project
root) and its content must be the CURRENT diff's hash, which only the CLI
can produce:

```bash
CEG="${CLAUDE_PLUGIN_ROOT:-$PWD}/core/hooks/council-escalation-gate.py"
python3 "$CEG" --council-flag set
```

This is the escape hatch `council-escalation-gate.py` checks first (TTL
`AGENT_COUNCIL_ACTIVE_TTL_S`, default 300s, bound to this diff's hash) —
without it, the same gate that routed the caller here would also deny the
council's own internal code-reviewer dispatch. Clear it again in step 6 once
the run is done — the flag only needs to cover this one dispatch, and a
still-fresh flag left behind would silently un-gate a later, unrelated diff.

### 1. Assemble the target

Default `--staged` (`git diff --staged`); `--head` reviews `HEAD~1..HEAD`; an
explicit `<range>` is passed through. Abort with a one-liner if the diff is
empty. Build ONE shared core, identical for every external lane:

- the diff, plus 3-5 lines of stated intent (from the commit message / user);
- the **impact context**, if any. Run
  `python3 "${CLAUDE_PLUGIN_ROOT:-$PWD}/core/infra/impact-context.py" <same target>`
  and paste its block verbatim; empty output means omit it. It names files that
  depend on the changed code but are not in the diff, plus the tests the change
  reaches. Ask each lane to check whether those outside callers still hold,
  because a reviewer who sees only the diff cannot see a caller it breaks.
  Findings about an outside caller must still quote a line from the diff
  (step 4's filter is unchanged);
- the output contract — findings only, each with:
  `file:line` · **verbatim quote of the offending line(s)** · severity
  (Blocker/Major/Minor/Note) · category · one-sentence rationale;
- "reply `NO-FINDINGS` if clean; do not restate the diff."

The verbatim-quote field is what makes step 4's hallucination filter possible
— a finding that cannot quote the code it indicts cannot be verified.

Each voting external lane's prompt = its **lens preamble** + the shared core
(decided 2026-08-20: perspective diversity over duplicate generalists — two
lanes reading one diff through different questions surface more than two
copies of the same question):

- **codex lens** (`second-opinion-review`) — implementation correctness:
  "does this diff do what it intends?" Logic errors, edge cases, off-by-one,
  error handling, concurrency, regressions.
- **gemini lens** (`third-opinion-review`) — architecture & consistency:
  "is this diff built the way the system is built?" Design and simplification
  opportunities, API/contract coherence, doc–code drift, cross-file
  consistency, naming.

A lens states emphasis, not permission — each preamble must also say "report
any defect you see, in or out of your lens." Grok (`advisor-third`) and the
OpenRouter free lane (`advisor-free`) both get the bare shared core, unscoped
by design: the advisory seats exist for the perspective the lenses didn't
assign. The Claude `code-reviewer` lane keeps its own agent charter untouched.

### 2. One cost confirmation

Count the external lanes about to run (2, plus 1 per optional advisor flag —
`--with-grok` and/or `--with-free`) and ask the user ONCE to approve the
calls. Only on approval set `AGENT_WORKER_YES=1` — per-invocation, never
exported into the session (the env-only gate contract in call-worker.sh: the
session that owns the user relationship asks first). Note for `--with-free`
specifically: the lane is free (no subscription cost), but it is
retention-flagged (the model behind it is commonly anonymous and may
retain/train on the diff) and cwd-guarded (`adapters/openrouter/README.md`
§ What "free" costs here) — say so as part of the same approval ask, not as
a separate prompt.

**Approval declined**: skip every external lane (no `call-worker.sh` calls at
all) and proceed with the Claude `code-reviewer` lane alone — this is the same
"single-vendor review" degrade the single-vendor warning (step 5) reports for,
so the report's first line must say so rather than borrow the council's
authority. This lane's own `code-reviewer` dispatch still needs the step 0.5
flag — the gate does not know approval was declined, only that the
council-active flag is fresh and bound to this diff's hash.

### 3. Dispatch all lanes in parallel

External lanes in the background (each capture path lands on stdout):

```bash
# Resolve the dispatcher from the plugin cache (plugin install) or the
# checkout (repo install) — never bare cwd-relative.
CW="${CLAUDE_PLUGIN_ROOT:-$PWD}/core/infra/call-worker.sh"
[[ -f "$CW" ]] || echo "council-review: call-worker.sh not found at $CW — harness root unresolved; external lanes are absent this run" >&2

# Bind every capture to this diff: sha256 over the staged blobs of risk-path files
# (empty when none). Pass it per call so the reviews.jsonl index row carries it.
# Only for --staged. For --head / <range> leave DK empty: the key describes the
# staged index, so a key for another diff would bind evidence to the wrong content.
DK=""
DIFF_SEL="${DIFF_SEL:---staged}"   # the selector this run reviews: --staged (default), --head or <range>
if [[ "$DIFF_SEL" == "--staged" ]]; then
  DK="$(python3 "${CLAUDE_PLUGIN_ROOT:-$PWD}/core/infra/review-evidence.py" key --staged)" \
    || { echo "council-review: diff key failed — dispatch without one" >&2; DK=""; }
fi

# per-lane prompts from step 1: lens preamble + shared core (grok: core only)
AGENT_WORKER_YES=1 AGENT_REVIEW_DIFF_KEY="$DK" bash "$CW" second-opinion-review < "$PROMPT_CODEX"  > "$CAP_DIR/codex.path" 2> "$CAP_DIR/codex.err" &
AGENT_WORKER_YES=1 AGENT_REVIEW_DIFF_KEY="$DK" bash "$CW" third-opinion-review  < "$PROMPT_GEMINI" > "$CAP_DIR/gemini.path" 2> "$CAP_DIR/gemini.err" &
# --with-grok only:
AGENT_WORKER_YES=1 AGENT_REVIEW_DIFF_KEY="$DK" bash "$CW" advisor-third         < "$PROMPT_CORE"   > "$CAP_DIR/grok.path" 2> "$CAP_DIR/grok.err" &
# --with-free only:
AGENT_WORKER_YES=1 AGENT_REVIEW_DIFF_KEY="$DK" bash "$CW" advisor-free          < "$PROMPT_CORE"   > "$CAP_DIR/free.path" 2> "$CAP_DIR/free.err" &
```

Captures and their index live outside the plugin cache, per project:
`~/.agent/workers/<project-key>/<ts>-<role>.md` plus one `reviews.jsonl` row per
capture (`diff_key` = `$DK`); override the directory with `AGENT_WORKERS_DIR`.
Lane telemetry goes to `~/.agent/logs/council-lanes.jsonl`.

The `[[ -f "$CW" ]]` line is a diagnostic, not a short-circuit: if the path is
missing the dispatches below still run and each exits 127, which the
degradation table already maps to "lane absent" — the echo just names the
unresolved root instead of leaving a bare shell error. A missing `$CW`
therefore means every external lane is reported absent this run (step 5's
single-vendor warning applies) — never substitute a different resolution.

Simultaneously dispatch the `code-reviewer` agent (Agent tool) on the same
diff — its frontmatter model pin stands; do not override. Then wait per lane
capturing each exit code (`wait "$pid_codex" || rc_codex=$?` …) — the
degradation table below keys off these codes, and the pre-dispatch refusal
path (exit 3) writes no capture file, so the exit code is the only signal for
it. Each lane's `timeout_s` in backends.json is the hard cap — no
sleep-polling.

### 4. Synthesize — in this order

1. **Mechanical truth first**: read each capture file's frontmatter; only
   `status: complete` lanes are seated. Anything else → the lane status table
   (step 5), with `unavailable_reason`/`fallback_reason` quoted.
2. **Dedup** across lanes: same file + overlapping lines + same essential
   issue = one finding, all sources credited.
3. **Citation check** — the hallucination filter: `Read` the actual file at
   each finding's `file:line` and compare against the finding's verbatim
   quote. No match after dedup → DROP the finding, and count drops per lane
   (reported — a lane's drop rate is signal about that lane).
4. **Severity re-rating**: re-rate every surviving finding against repo
   context yourself; external self-ratings are input, not verdict.
5. **Source tagging**: `[claude]` `[codex]` `[gemini]` `[grok:advisory]`
   `[free:advisory]`. Findings reached independently by ≥2 seated vendors are
   marked **high-signal** and listed first. Lenses don't weaken this rule — a
   defect two lanes reached through *different* questions is, if anything,
   stronger agreement than two copies of the same question would give.
6. **Disagreements**: where seated lanes conflict, show both positions and
   adjudicate with a stated reason — against the code, not against authority.
7. **Grok section** (when present): separate, labeled
   "advisory — not part of the verdict"; its findings never flip severity or
   the overall verdict.
8. **Free-lane section** (when present): same treatment as the grok section
   — separate, labeled "advisory — not part of the verdict"; its findings
   never flip severity or the overall verdict.

Security-shaped findings (auth, injection, secrets, crypto) are LISTED but
not adjudicated here — route them to `security-reviewer` (the
no-double-reporting contract in agents/code-reviewer.md applies to the
council too).

### 5. Report

The report header is produced mechanically, never composed by hand. Pass the capture
path of every lane that ran (each `call-worker.sh` stdout line):

```bash
python3 "${CLAUDE_PLUGIN_ROOT:-$PWD}/core/infra/review-evidence.py" summary <capture paths>
```

Paste its output verbatim at the top of the report. When no external-vendor lane
returned `complete`, its first line is `single-vendor review — not a council (no
external lane returned)` — keep it; the Claude lane always runs but must not borrow
the council's authority. Absent lanes (no capture) are appended by hand as
`<lane> ✗ absent (<reason>)`.

```
## Council review — <target>
<review-evidence.py summary output: [single-vendor warning,] Lane status: ...>
Citation drops: codex 1, gemini 0

### High-signal (≥2 vendors)  ...
### Findings                  ...
### Disagreements             ...
### Advisory (grok)           ...
### Advisory (free)           ...
```

The commit gate (`core/git-hooks/pre-commit`) blocks risk-area commits that have no
external `complete` review bound to the staged content. The user-only escape
`AGENT_REVIEW_OVERRIDE="<reason>"` exists for the case where every external lane is
down; an agent must never set it itself (`pre-tool-guard.sh` denies a Bash command that
sets the recognised assignment forms, and logs the attempt; a speed bump, not a boundary).
Risk areas are classified by `core/infra/council-threshold.sh`: migration,
secret, edge-fn, billing, and auth (code files only: an auth-family directory such as
`auth/`, `oauth/`, `authn/`, `authz/`, `authentication/`, `authorize*/`, `login/`, or a file
named `auth*`/`login*`/`*_auth*`; `author*` never matches). Out of the auth class: sso, session, jwt, token*, password*,
rbac/acl/iam, oidc/saml, mfa/otp, signin/signup, logout, and non-code policy files (only
`.rego` counts).

During a merge, a risk file whose staged state equals a merged-in parent's is not re-gated
when a remote DEFAULT branch (the target of `refs/remotes/<remote>/HEAD`, else
`<remote>/main`/`master`) contains that parent (any parent of an octopus). Remote-tracking
refs are local state an agent could rewrite, so this is a convenience for merging the
default branch in, not proof of review; a local branch, a scratch branch pushed to the
remote and a hand-written `MERGE_HEAD` earn no exemption. `core/git-hooks/pre-merge-commit`
runs the same check for auto-merge commits and passes the merged heads explicitly. Observed
limit: `git cherry-pick` and `git rebase` commit without running pre-commit at all, so they
are not gated.

Limits of the gate, stated plainly. The `pre-tool-guard.sh` guards (`review-override` for
`AGENT_REVIEW_OVERRIDE`/`AGENT_WORKERS_DIR`/`AGENT_LOGS_DIR`/`GITHEAD_*`, `evidence-ledger`
for `reviews.jsonl`/`review-override.jsonl`) are Bash-tool speed bumps: an interpreter
one-liner (python/perl/node -e) that sets env or writes files, and a script written then
run, evade them. The ledger row binds the diff key to "an external lane returned complete
while this key was set", not to the bytes the vendor actually read. In this framework repo
the gate code itself (`core/git-hooks`, `core/infra/review-evidence.py`,
`core/infra/council-threshold.sh`, `core/hooks/pre-tool-guard.sh`) is editable in-tree.

### 6. Clear the council-active flag

The flag set in step 0.5 must not outlive this run — a still-fresh flag left
behind would silently un-gate any LATER, unrelated council-scale diff for the
rest of its TTL. Clear it once the report above is produced, whether the run
succeeded or degraded:

```bash
CEG="${CLAUDE_PLUGIN_ROOT:-$PWD}/core/hooks/council-escalation-gate.py"
python3 "$CEG" --council-flag clear
```

## Degradation

| Signal | Meaning | Action |
|---|---|---|
| call-worker exit 3 | not approved | re-ask or drop the lane |
| exit 127 / `status: unavailable` | lane disabled / CLI or preflight missing | lane absent, quote reason |
| exit 124 / `status: timeout` | hung CLI killed | lane absent |
| exit 1 / `status: rate-limited` | vendor quota/rate limit hit (e.g. grok/openrouter free tier) | lane absent — fail-open; report "rate-limited, retry later", never an upgrade pitch |
| exit 1 / `status: failed` | backend errored | lane absent, quote stderr tail |

External failures never abort the review — the Claude lane carries it.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.