
Codex Council
- 1 repo stars
- Updated July 13, 2026
- ehzawad/codex-council
Multi-perspective parallel Codex review with N sub-agents, each framed with a role you decide per call.
About
codex-council is a Claude Code skill in the AI & Agent Building category. Multi-perspective parallel Codex review with N sub-agents, each framed with a role you decide per call.
- codex-council
- AI & Agent Building
- AI-coding skill
Codex Council by the numbers
- Data as of Jul 13, 2026 (Skillselion catalog sync)
/plugin marketplace add ehzawad/codex-council/plugin install codex-council@codex-councilAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| repo stars | ★ 1 |
|---|---|
| Last updated | July 13, 2026 |
| Repository | ehzawad/codex-council ↗ |
What it does
Multi-perspective parallel Codex review with N sub-agents, each framed with a role you decide per call.
README.md
codex-council
A Claude Code plugin that fans out the same context to N parallel OpenAI Codex sub-agents, each framed with a role you decide per call. You, Claude, and a council of Codex perspectives in the loop. Install once; invoke from any Claude Code project.
Prerequisites
- Claude Code — authenticated (
claudein terminal) - OpenAI Codex CLI — authenticated (
codexin terminal)
Both must be logged in and working in your terminal before using this plugin.
Install
claude plugins marketplace update
claude plugins marketplace add ehzawad/codex-council
claude plugins install codex-council@codex-council
Persists across sessions — no flags needed.
Iterating on the plugin itself? See For development for the author dev loop (symlink + SessionStart hook).
Usage
/codex-council:codex-council
Claude looks at the current task, the relevant files or artifacts in flight, and the judgment the user actually needs, composes a tailored 2–6 agent panel, announces it briefly, and launches without a manual launch approval gate. Each role keeps its own Codex thread per project and host session, so framings accumulate across calls in the same terminal/session if you reuse role IDs.
Auto-trigger phrases (natural language) are restricted to:
ask codex council
codex council review
reconcile with codex team
codex team reconciliation
Broader phrases like "agent team," "agents in parallel," "subagents,"
"council review," "panel review," "codex agent team," "codex panel,"
or "fan out to codex agents" do not trigger this skill — they
route to Claude Code's built-in Agent tool (Claude subagents, a
different mechanism). The skill's SKILL.md enforces this with a
disambiguation gate.
There is no built-in role catalog. Claude composes the panel
on-the-fly per invocation — ultrathinking about the task, drafting
role ids, labels, and instructions tailored to what the user is
actually doing, then passing them to the script via --roles-file
(a path to the panel JSON) and --context-file (a staged context file
in the same private per-run directory). The script is a pure
orchestrator: it reads those staged inputs, validates them before
launch, fans out parallel codex exec subprocesses, and aggregates
the replies.
Note on fit. Codex is strongest where the task has technical, structured, or evidence-checking surfaces. For tasks where a single Claude pass is likely as good or better than a Codex council, don't force a council — say so.
The JSON role spec, retries, and panel-proposal flow are documented in
plugins/codex-council/skills/codex-council/SKILL.md.
Architecture
flowchart LR
User["User"] --> Claude["Claude Code"]
Claude --> Skill["codex-council skill<br/>SKILL.md"]
Skill --> Panel["Compose task-specific role panel<br/>Announce and launch"]
Panel --> Script["codex_council.py<br/>--roles-file + --context-file"]
subgraph Plugin["codex-council plugin"]
Manifest[".claude-plugin/plugin.json"] -.-> Skill
Script --> Validate["Validate staged inputs<br/>Parse and validate roles"]
Validate --> Prompt["Bookend context with<br/>each role instruction"]
Prompt --> Fanout["asyncio.gather<br/>parallel fan-out"]
Fanout --> RoleA["Role runner A"]
Fanout --> RoleB["Role runner B"]
Fanout --> RoleN["Role runner N"]
RoleA <--> State["Per-project/session/role state<br/>$XDG_STATE_HOME/codex-council"]
RoleB <--> State
RoleN <--> State
end
subgraph Codex["Codex CLI subprocesses"]
RoleA --> ExecA["codex exec resume or fresh"]
RoleB --> ExecB["codex exec resume or fresh"]
RoleN --> ExecN["codex exec resume or fresh"]
end
ExecA --> JSONL["JSONL events"]
ExecB --> JSONL
ExecN --> JSONL
JSONL --> Parse["Extract thread.started<br/>Extract final agent_message"]
Parse --> Report["Aggregated markdown report"]
Report --> Claude
Claude --> Reconcile["Claude reconciles results<br/>for the user"]
Launch Flow
sequenceDiagram
participant U as User
participant C as Claude Code
participant F as Private run dir
participant S as codex_council.py
participant X as Codex CLI
U->>C: Invoke codex-council
C->>C: Compose task-specific role panel
C->>U: Announce panel
C->>F: mktemp -d once
C->>F: Write roles.json and context.md
C->>S: --check-staging-dir F
S-->>C: staging OK or precise staging error
C->>S: --roles-file F/roles.json --context-file F/context.md
par role fan-out
S->>X: codex exec role A
S->>X: codex exec role B
S->>X: codex exec role N
end
X-->>S: JSONL events
S->>F: out.md report
S->>F: err.log progress + CODEX_COUNCIL_DONE
C->>F: Read out.md and err.log
C->>U: Reconciled answer
State Scope
flowchart TD
Project["Git repo root or cwd"] --> ProjectHash["project hash"]
Role["Role id"] --> RoleKey["role key"]
Explicit["CODEX_COUNCIL_SESSION_KEY"] --> Scope{"explicit key set?"}
Auto["Auto-detected host session<br/>Claude session, CODEX_THREAD_ID,<br/>TERM_SESSION_ID, TMUX_PANE, STY, VSCODE_PID"] --> Scope
Disable["CODEX_COUNCIL_DISABLE_AUTO_SESSION_KEY=1"] --> Scope
Scope -->|"explicit"| SessionHash["session hash"]
Scope -->|"auto"| SessionHash
Scope -->|"disabled or unavailable"| ProjectOnly["project-wide scope"]
ProjectHash --> StatePath["state path"]
SessionHash --> StatePath
ProjectOnly --> StatePath
RoleKey --> StatePath
StatePath --> Lock["POSIX lock per state file"]
Lock --> Resume["resume stored Codex thread"]
Lock --> Fresh["or start fresh thread"]
State
Council state lives at
$XDG_STATE_HOME/codex-council/{project-hash}-{session-hash}__{role-id}.json
when the runner can detect a stable host-session id. It auto-detects common
values such as Claude session ids, CODEX_THREAD_ID, TERM_SESSION_ID,
TMUX_PANE, STY, and VSCODE_PID, so separate terminal tabs/panes in the
same repo do not normally share role threads (except multiple integrated
terminals in the same VS Code window, which share VSCODE_PID; set
CODEX_COUNCIL_SESSION_KEY to isolate those). Follow-up calls from the same
host session still resume the same per-role thread.
CODEX_COUNCIL_SESSION_KEY remains an explicit override for custom scoping
per branch or task. Set CODEX_COUNCIL_DISABLE_AUTO_SESSION_KEY=1 only if you
want the older project-wide state file shape:
{project-hash}__{role-id}.json.
Security
Codex runs with --dangerously-bypass-approvals-and-sandbox — no
approval prompts, no filesystem sandbox. This gives every Codex
sub-agent full read/write access to your machine so it can thoroughly
inspect the project. Do not use this plugin on untrusted projects or
with untrusted input — a prompt injection inside reviewed content can
steer all N agents.
The same bypass applies when reviewing any non-code material — a prompt injection inside a Markdown draft, a CSV column header, or a research excerpt is just as effective as one inside a code diff, and non-code content has historically been less hardened against injection than code review flows. Be deliberate about what you pipe in.
Configuration
The script uses your Codex CLI defaults — model, reasoning effort, and
other settings come from ~/.codex/config.toml. No model is hardcoded.
Sandbox and approval settings are overridden by the plugin (see
Security above).
No wall-clock timeout is enforced — neither the council nor codex exec imposes a run-level deadline, so a role runs as long as Codex
takes, hours or days. An actively-working role streams continuously,
so codex's per-request stream-idle guard never applies to it; that
guard only covers a stalled connection (and is retried). To widen it
for very long quiet stretches, raise
model_providers.<id>.stream_idle_timeout_ms and the retry counts in
your own ~/.codex/config.toml. Ctrl+C tears down every codex process
group.
For development
git clone https://github.com/ehzawad/codex-council.git
cd codex-council
claude plugins marketplace add ehzawad/codex-council # skip if already added
claude plugins install codex-council@codex-council # skip if already installed
./scripts/dev-link.sh
# restart Claude Code once
scripts/dev-link.sh does three things:
- Creates a symlink at
~/.claude/plugins/cache/codex-council/codex-council/<version>/→ this repo's working tree, so edits are live at runtime. - Rewrites
~/.claude/plugins/installed_plugins.jsonso the harness'sinstallPathandversionfields point at the symlinked version. - Prunes any stale sibling entries in the cache dir for other versions, so bumping
plugin.jsonand re-running dev-link doesn't leave old directories or symlinks behind.
Step 2 is load-bearing: the harness loads whichever installPath the manifest declares, not whichever symlinks exist in the cache. Without the manifest rewrite, bumping the version in plugin.json and re-running dev-link creates a new symlink that the harness will happily ignore.
After the one-time restart, edits to plugins/codex-council/** are live on the next /codex-council:codex-council invocation. SKILL.md caveat: the Claude Code harness's skill-content caching behavior is not documented, so SKILL.md edits may still require a session restart; the script and the rest of the plugin files update live.
Startup-overwrites-symlink caveat. Claude Code re-validates the plugin cache on every session start and replaces the symlink with a freshly-fetched copy from origin. The documented "symlinks are preserved" property applies to runtime resolution, not startup validation. Two ways to handle it:
- Manual: re-run
./scripts/dev-link.shafter every Claude Code restart, anyclaude plugins update, any version bump inplugin.json(the cache path changes with the version), or any cache wipe. - Automatic (recommended): add a
SessionStarthook to~/.claude/settings.jsonso the symlink is re-established on every session:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash -lc 'mkdir -p \"$HOME/.claude/logs\"; log=\"$HOME/.claude/logs/codex-council-dev-link.log\"; \"/absolute/path/to/codex-council/scripts/dev-link.sh\" >>\"$log\" 2>&1; rc=$?; if [ \"$rc\" -ne 0 ]; then printf \"%s dev-link failed exit=%s\\n\" \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" \"$rc\" >>\"$log\"; fi; exit 0'"
}
]
}
]
}
}
Failures remain fail-open (exit 0) so a missing repo or broken dev-link script
never blocks session startup, but diagnostics are logged to
~/.claude/logs/codex-council-dev-link.log. Keep this fail-open behavior limited
to the development startup hook; council launch/context pipelines in SKILL.md
should fail closed with set -euo pipefail. Merge into your existing
hooks.SessionStart array if you already have one (don't replace it).
License
MIT