Policies

How claw-drive decides what Session B can do.

The policy object

A policy is either the string "bypass" — no gating, useful for demos, never for real work — or an object with up to four rule lists plus two scalars:

{
  "auto_approve": [
    { "tool": "Read" },
    { "tool": "Bash", "bash_command_matches": "^git (status|diff|log|show|branch) " }
  ],
  "auto_defer": [
    { "tool": "Bash", "bash_command_matches": "^sudo\\s", "name": "sudo → human" }
  ],
  "auto_reject": [
    { "tool": "Bash", "bash_command_matches": "rm -rf |git push ", "severity": "high" }
  ],
  "escalate_default": true,
  "decision_timeout_seconds": 3600
}

Rule shape

Evaluation order

Rules are checked in this order:

auto_reject → auto_defer → auto_approve → escalate_default
Asymmetric risk. The stricter rule wins when lists overlap. A false-reject is a human prompt — reversible. A false-approve is a silent bypass — not.

This closes compound-command bypasses like:

git status && rm -rf /tmp
# git status matches auto_approve, rm -rf /tmp matches auto_reject → reject wins.

Templates

Two policy templates ship in templates/:

The CLAW-GATE convention

The default policy includes a review-gate defer rule:

"auto_defer": [
  { "tool": "Bash", "bash_command_matches": "^echo 'CLAW-GATE:", "name": "review gate" }
]

In the scenario brief, tell B:

Before each risky step, run echo 'CLAW-GATE: <your question>' with the Bash tool and wait for my response.

When B runs the echo, the defer rule fires. The monitor alerts A. The human answers. A calls provide_tool_output with the answer. B reads it and proceeds. No new primitive — same mechanism as a sudo defer.

Learning from decisions: remember_as_policy

When you approve, reject, or defer a call interactively, pass remember_as_policy: true to have claw-drive derive a rule and append it to the session's live policy.

Derivation is narrow by default for non-Bash tools:

Tools without a recognised identifying arg (TodoWrite, ExitPlanMode, custom MCP tools) fall back to tool-wide matching. The derived rule's name is tagged (tool-wide fallback) so it's obvious at review time.

Bash remembering derives from a prefix of the command, same as before.

Testing a policy

Use claw-drive policy-test '<command>' to see which list fires and what the resolved decision is — no session spin-up required:

$ claw-drive policy-test 'kill -9 1'
Decision:       escalate
Default action: defer
List:           auto_defer
Matched rule:   "kill -9 of init/everything/process-group (PID 1, -1, or 0)"
Pattern:        \bkill\s+-9\b.*?\s(-1|0|1)(\s|$|[;&|])
Severity:       high
Tool:           Bash
Command:        kill -9 1
Policy:         <repo>/templates/claw-drive-policy.json (starter)

Three output formats: human (default), --explain (walks every rule with / marks), --json (single-line for piping to jq and CI tooling).

Bash uses positional shorthand; non-Bash tools take --tool TOOL --arg KEY=VALUE (repeatable):

claw-drive policy-test --tool Read --arg file_path=/etc/passwd
claw-drive policy-test --tool Edit --arg file_path=/tmp/foo.ts --arg old_string=secret
claw-drive policy-test --tool Grep --arg pattern='api_key'

--policy SPEC selects which policy to test against. SPEC is one of starter (default), permissive, bypass, or a <path> to a custom policy JSON. --exit-on reject|defer|approve|escalate turns the diagnostic into a CI gate — exits 1 on match, 0 otherwise.

Timeouts

The default decision_timeout_seconds is 3600 (1 hour). If an escalation sits unresolved that long, the runner fires the rule's default_action:

Override per-session via start_session's decision_timeout_seconds argument, or per-policy via the field above.

History note. v0.1's default was 300 seconds — small enough to auto-approve sensitive calls silently when a driver's monitor had a transient gap. v0.2 widened to 3600s.