Feature Request: Built-in Peer Model Audit Gate for Code Quality Enforcement

Resolved 💬 10 comments Opened Mar 15, 2026 by berrzebb Closed May 22, 2026

Summary

I've been running a production workflow where GPT (acting as an independent auditor) reviews Claude Code's output before any work can be marked complete. After several months of iteration, this cross-model audit loop has proven significantly more effective than prompt-based instructions alone. I'd like to propose this as a native Claude Code feature.

---

The Problem: Single-Model Blind Spots

Claude Code has a well-documented tendency to:

  • Mark work as complete when edge cases remain untested (test-gap)
  • Drift from original claims in evidence documentation (claim-drift)
  • Self-validate outputs that a peer reviewer would immediately flag

Instruction-based corrections ("always write tests for hook functions") are absorbed but fade across sessions or are rationalized away under time pressure. The model cannot reliably catch its own blind spots through self-review.

---

The Solution: Cross-Model Audit Gate (Working Implementation)

I've implemented this as a Claude Code hook workflow:

Architecture

PostToolUse (any file edit) -> index.mjs
        |
        +- watch_file + trigger_tag?
        |       |
        |       +- audit.lock exists? -> skip (already running)
        |       +- spawn audit.mjs (detached, background)
        |              -> hook returns immediately (non-blocking)
        |              -> audit-bg.log streams real-time NDJSON output
        |              -> audit.lock created (PID + TTL)
        |              -> agent auto-registers Cron watcher
        |
        |   ... audit runs in background (Codex review) ...
        |
        |       audit.mjs completes:
        |              -> gpt.md created/updated
        |              -> respond.mjs (tag sync)
        |              -> audit.lock deleted
        |              v
        |   [Detection: Cron watcher OR next PostToolUse]
        |              v
        |    +--- [agree_tag] ----- retrospective.mjs
        |    |                           v
        |    |                    retro-marker set
        |    |                           v
        |    |                    session-gate blocks Bash
        |    |                           v
        |    |                    HITL retrospective (user + AI)
        |    |                           v
        |    |                    echo session-self-improvement-complete
        |    |                           v
        |    |                    git commit allowed
        |    |
        |    +--- [pending_tag] -> respond.mjs --auto-fix -> correction
        |
        +- gpt.md newer? -> respond.mjs (auto-sync)
        +- planning file? -> respond.mjs --gpt-only
        +- quality rule? -> run command (ESLint, npm audit, ...)

Reentrance: FEEDBACK_LOOP_ACTIVE env var
Lock: audit.lock with PID + TTL (30min) -- prevents concurrent audits

Key Design Decisions

1. Async Non-Blocking Audit (Critical for UX)
The audit runs as a detached background process (spawn(detached: true)). The PostToolUse hook returns immediately -- the user continues working while Codex reviews in the background. Real-time streaming via NDJSON line-by-line parsing writes agent messages to audit-bg.log. A lock file with TTL prevents concurrent audits and handles PID recycling.

2. Session Reuse (Critical for Performance)
The auditor (GPT) maintains a single session across all audit rounds. On first run, it reads the full codebase. On subsequent rounds, it only reads the delta (changed files + new evidence). Without session reuse, every audit requires full codebase re-read -- making this impractical at scale.

3. Gate Enforcement (Not Advisory)
The audit result is not a suggestion. Work cannot be marked [agree_tag] until the peer model explicitly approves. Claude cannot self-promote past the gate.

4. Structured Evidence Format
Claude submits a Claim + Changed Files + Test Command + Test Results package. GPT responds with specific rejection codes (test-gap [major], claim-drift [minor]) referencing exact file:line locations. This forces precision on both sides.

5. HITL Retrospective Gate
After all items reach agree_tag, retrospective.mjs sets a marker. session-gate.mjs (PreToolUse hook) blocks Bash/Agent until the human-in-the-loop retrospective is completed. Only echo session-self-improvement-complete clears the gate.

6. Facade Pattern for Policy
Prompt templates are lean facades (~30 lines) that reference policy files via {{REFERENCES_DIR}}. To change audit criteria, teams edit templates/references/{locale}/rejection-codes.md -- no code changes needed.

Real Example: The Non-Standard Tag Vicious Cycle

A subtle bug demonstrated the importance of structural enforcement over instruction:

The audit prompt told Codex to use human-readable labels as judgment types. But the consensus engine expected machine-parseable status tags. Codex used a non-standard tag that the engine didn't recognize -- causing an infinite loop:

Codex writes [non-standard-tag] -> respond.mjs searches for [pending_tag] -> no match
-> "no response" -> next audit -> same result -> infinite loop

Fix required two structural changes:

  1. {{REFERENCES_DIR}} -- Codex couldn't read output-format.md because the path was wrong (relative to wrong root)
  2. Explicit tag enforcement in the prompt -- only [agree_tag] or [pending_tag] allowed, all other labels banned

This bug could not be caught by self-review. It required structural analysis of the tag flow across three components.

---

Proposed Native Feature

Option A: --audit-mode flag for Claude Code sessions

claude --audit-mode --audit-model gpt-4o --audit-session-persist
  • After each significant output, a second model instance reviews the work
  • Session is reused across the project lifecycle (not reset per review)
  • Claude cannot mark tasks complete until the audit model approves

Option B: Native Multi-Agent Review Workflow API

A first-class workflow primitive:

# .claude/audit.yml
auditor:
  model: claude-opus-4-5  # or external model via API
  session: persist         # maintain context across rounds
  gate: required           # block completion without approval
  format: structured       # claim/evidence/verdict schema
  async: true              # non-blocking background execution

Option C: Structured Self-Critique Pass

A lighter version: before marking any task complete, Claude is required to run a separate "adversarial review" pass with an explicitly contrarian system prompt. Less powerful than a true peer model, but deployable immediately without multi-model infrastructure.

---

Why This Matters for the Product

The core insight is: behavioral constraints enforced by structure are more reliable than behavioral constraints enforced by instruction.

You cannot instruct Claude to consistently catch its own test-gap issues across sessions. But you can build a gate that makes it structurally impossible to proceed until a peer model confirms the gap is closed.

This is especially valuable for:

  • Enterprise code review workflows where quality gates are mandatory
  • Long-running agentic tasks where drift accumulates over many steps
  • Security-sensitive code where self-validation is insufficient by design

---

Implementation Notes from the Working System

  • Async audit: spawn(detached: true) + child.unref() -- hook returns immediately, Codex runs in background. Lock file (audit.lock) with PID + 30-minute TTL prevents concurrent runs and handles PID recycling
  • Streaming output: streamCodexOutput() -- NDJSON events parsed line-by-line in real-time, agent messages written to audit-bg.log as they arrive
  • Cross-platform: cli-runner.mjs handles Windows .cmd/.bat/.ps1 for both sync (spawnResolved) and async (spawnResolvedAsync) spawning
  • Hook trigger: PostToolUse(Edit|Write) watching for trigger_tag in watch_file -- fully configurable
  • Session persistence: Thread ID stored in session.id -- session resets only when all items are approved
  • Auto-sync: When gpt.md is newer than watch_file, respond.mjs promotes agree_tag items directly and feeds pending_tag corrections back to claude -p automatically
  • Facade prompts: {{REFERENCES_DIR}} resolves to correct path relative to repo root at runtime -- policy files readable by any auditor model regardless of installation path
  • Tag enforcement: Audit prompt explicitly lists allowed tags and bans non-standard labels
  • Quality gates: quality_rules in config.json map file patterns to lint/audit commands -- run inline on every matching edit
  • Session gate (HITL): session-gate.mjs (PreToolUse) blocks Bash/Agent until retrospective completion -- session-aware (only blocks the completing session)
  • Shared context: context.mjs -- single source for config, paths, parsers, i18n (no duplication across 6+ scripts)
  • i18n: All user-facing strings externalized to locales/{en,ko}.json -- configurable locale via plugin.locale

---

References

Open-source implementation: berrzebb/consensus-loop

consensus-loop/
+-- context.mjs            <- Shared module: config, paths, parsers, i18n cache
+-- index.mjs              <- PostToolUse hook: async audit dispatch + quality gates
+-- audit.mjs              <- Background Codex audit with streaming NDJSON output
+-- respond.mjs            <- Tag sync engine: promote / demote / auto-fix
+-- retrospective.mjs      <- Sets retro marker after all items agreed
+-- session-gate.mjs       <- PreToolUse: HITL gate (blocks Bash until retro complete)
+-- cli-runner.mjs         <- Cross-platform binary resolver (sync + async spawn)
+-- i18n.mjs               <- Standalone locale helper (fallback for non-context imports)
+-- locales/{en,ko}.json   <- All user-facing strings
+-- templates/
|   +-- *.md               <- Facade prompts (~30 lines each, {{REFERENCES_DIR}} injection)
|   +-- references/{ko,en}/ <- Team-editable policy files
+-- examples/
|   +-- config.example.json       <- Annotated full config reference
|   +-- templates/{en,ko}/        <- Starting points for prompt templates
+-- docs/{en,ko}/README.md        <- Full plugin reference
  • Evidence format: watch_file (author) + respond_file (auditor verdict) -- paths fully configurable in config.json

---

Note: This issue was written with the assistance of Claude Code (claude-opus-4-6).

View original on GitHub ↗

This issue has 10 comments on GitHub. Read the full discussion on GitHub ↗