---
name: od-guards
description: Guard tooling incidents — shim/gate/wrapper misfire, exit 97, denied command, prevent-band block, cpu-guard/local-gate, claudex/cdx/cursor-agent wrapper confusion, slopgate, throttled/killed session. Triggers on shim, gate, guard, blocked, refused, exit 97, slopgate, prevent-band, throttled. Invoke BEFORE exporting bypass env vars.
---

# Guard tooling — incident playbook

audience: AI coding agents first. BLUF: walk EVERY wrapper layer → test both branches → guard fix must fail-closed.

## Covers

**Absorbs:** `tool-reliability-fatigue` (shim/gate/wrapper subset — ids C133, C134, C136, C150, C173, C174, C193 per taxonomy `_meta`).

**Keywords:** shim, gate, wrapper, guard, deny, blocked, refused, exit 97, claudex, cdx, cursor-agent, cpu-guard, slopgate, prevent-band, lock, sqlite, throttled, killed session, statusline.

**Registry coverage:** 7 of 217 (2026-08-08). Recurring: agent blames tool when wrapper blocked; bypass env exported; wrong branch (workstation vs buildbox) untested.

## Doctrine

Check EVERY wrapper layer before blaming the tool. Test both branches. Guard fix must fail closed.

## First steps — layer cake, bottom up

1. **Exit code table:** 97 = local-dispatch-guard / headless-guard; 84 = uncontainable; 81 = credential; 80 = no node; 3 = precondition. Read `od-auth` exit table — do not guess from message text.
2. **PATH order:** which binary ran? `type -a cdx`, `type -a cursor-agent` — shim vs real. `~/.local/bin` vs `~/.claude/bin`.
3. **Wrapper chain:** cpu-guard → local-gate → admission queue → deny-gate hook. Command may pass one layer, fail next.
4. **Both branches:** guard must pass on allowed host/path AND refuse on forbidden. Test workstation headless launch (must 97) AND buildbox launch (must pass).
5. **prevent-band / slopgate:** BLOCK = fix finding or justify benign — never `--no-verify`. Report-only WARN still MUST-address per CLAUDE.md.
6. **NEVER bypass:** `OD_LOCAL_DISPATCH_OK`, `E2E_REMOTE_OK`, `HARNESS_SEAT_CONTAINER` — owner-only escapes, not yours.
7. **Fix shape:** single wrapper, fail-closed on unexpected state, env scoped to subprocess, registry in `tools.json` for deny-gate.

## Never-touch — applies here

From `modules/workstation/claude/incidents/never-touch.md`:

- NEVER bypass admission queue or prevent-band gates.
- NEVER pass `--account`/`--profile` to `cdx` unless task genuinely requires that account.
- NEVER export guard bypass env vars.

## Placement map — read these paths

`modules/workstation/claude/incidents/placement-map.md`:

- `~/.claude/bin/` — guards, shims, `local-dispatch-guard`, `install-headless-guard`.
- `~/.claude/lib/cpu-guard.sh` — resource wrapper canonical path.
- `~/.claude/hooks/deny-gate.mjs` — PreToolUse deny registry (`tools.json`).
- `~/.claude/buildbox-hosts.json` — host reachability for dispatch guards.

## Resolve — exact CLI (never guess syntax)

```
od-incidents list [--type guard-tooling] [--state S]
od-incidents show <id>
od-incidents search <query>
od-incidents resolve <id> --artifact <ref> [--summary <line>]
```

`resolve` REFUSES without `--artifact` (exit 2). Artifact = guard test both branches green, or landed sha + command showing correct refusal/pass.

## Self-update — MANDATORY

This doc is the fleet's memory. You changed the architecture, wiring, commands, or
doctrine this skill describes — or a live incident just proved a rule here wrong or
missing → EDIT THIS FILE in the same landing as the change. Adopted AND rejected
decisions both go in, with the measured why. An owner reminder to record a lesson is
a failure of this rule, not the trigger for it.
