Reverse-direction Claude gate (codex-claude-admit + claude PATH shim)¶
Why¶
The governor's PreToolUse hook gates Claude's own Agent/Workflow spawns. It does not see the
reverse direction: an external harness — Codex/ChatGPT, fleet-dispatch, an llm-council seat — invoking
the native claude CLI, which spends the same 5h Claude budget. On 2026-07-21 a Codex loop ran
claude -p --model claude-fable-5 repeatedly and burned the entire window before any burn-scan
recomputed the tier. Seven ungated call sites were found, all resolving claude through PATH.
Design¶
Two defenses in the codex-claude-admit verb (CodexClaudePolicy + CodexClaudeLedger):
- Tier gate — deny on native
RED/BLACKOUT(budget already spent). No governor state → fail closed. - Runaway call-cap —
codexClaudeMaxCallsPer5h(12) /codexClaudeMaxCallsPerDay(40), booked atomically incodex-claude-ledger.jsonl. This bites before a burn-scan flips the tier — the lag the incident exploited.YELLOWadmits but advises a cheaper model.
The verb is fronted by a claude PATH shim (shim/claude POSIX + shim/claude.cmd) installed
ahead of the real binary. It runs the admit and execs the real claude.exe only on exit 0. It
gates only non-interactive claude -p/--print; interactive claude passes straight through, so a
human launching Claude Code is untouched.
Metering caveat (Kerr)¶
The cap counts calls, a proxy for burn. A few very large prompts can concentrate cost into fewer
than cap calls. The tier gate (native RED/BLACKOUT) is the token-spend backstop; the cap bounds
call-rate. Do not read "under 12 calls" as "safe spend".
Controls¶
| Control | Effect |
|---|---|
UG_CLAUDE_ADMIT_BYPASS=1 |
Human break-glass — skip the gate for this call (POSIX: =1; cmd: ==1). |
UG_CLAUDE_ADMIT_FAIL_OPEN=1 |
If the governor exe is missing, allow instead of the default fail-closed. |
config.json codexClaudeGateEnabled=false |
Kill-switch — admit everything (loud on stderr). |
codexClaudeMaxCallsPer5h / codexClaudeMaxCallsPerDay |
Tune the runaway caps. |
Install¶
install.ps1 calls install-claude-shim.ps1, which copies the exe to a stable
~/.claude/usage-governor/bin/UsageGovernor.exe, renders the shims with that path, and prepends the
shim dir to the USER PATH. Restart Codex / fleet-dispatch listeners after install so they inherit
the new PATH.
bin/ has exactly one writer, sync-governor-bin.ps1, called by both install-claude-shim.ps1 and a
master deploy.ps1 (no -NoPull), which then asserts the bin copy reports the just-built commit.
Before FLT-269 only the shim installer wrote bin/, so two months of deploys refreshed publish/
while every PC's callers kept running a 2026-07-22 build without grok-exec. deploy.ps1 -NoPull
(worktree verification, CI) deliberately leaves bin/ alone.
Absolute-path residual — CLOSED caller-side (FLT-176)¶
A PATH shim only intercepts callers that resolve claude by name. A caller that invokes the
absolute ...\.local\bin\claude.exe bypasses it. All seven known incident paths use bare claude, so
they were already covered — but callers should not rely on PATH ordering alone. This is now enforced
caller-side at every trusted caller: each admits explicitly via UsageGovernor codex-claude-admit
before launching claude, then runs it with UG_CLAUDE_ADMIT_BYPASS=1 on the child so the shim never
double-books the already-admitted call. Missing/broken governor fails closed
(UG_CLAUDE_ADMIT_FAIL_OPEN=1 break-glass).
- fleet-dispatch (master
7509af5+ parityec7b992):ClaudeAdmitGate— one admit at theRealJobExecutor.RunClaudeAsyncorchestrator, covering the stream-json path and both legacy-argv fallbacks; also gates therun_agentClaudeAgentRunnershell adapter (an 8th path found in review). - llm-council (master
567a760):ProcessCliRunnergates theclaudeseat (quote-stripped basename match) before prompt-file staging. - codex-harness-mirror (master
55db22c): the 4 skills carry inline PS admit guards; the two council skills soft-drop a denied Claude seat instead of aborting the council.
Each landed behind its own council-code-review gate. Gate-design notes for future callers: fail
closed on a Process.Start exception (not only a null return); bound the post-exit pipe drain and
the admit wait (5s) so a wedged/child-holding governor cannot hang the caller; strip surrounding quotes
before a basename match. Resolve the governor exe: FLEET_USAGE_GOVERNOR env →
~/.claude/usage-governor/bin/UsageGovernor.exe → the publish path.