The CLI is published on npm (npm i -g @aex-lang/cli). APIs are experimental and may change before v1.0. Roadmap →
Skip to content

Claude Code

AEX provides three ways to enforce policies with Claude Code:

  1. aex gate (recommended) — PreToolUse hook that gates every built-in tool call (Read, Write, Bash, etc.) against your policy
  2. aex proxy — MCP proxy that gates tool calls from upstream MCP servers
  3. Both together — full coverage of built-in and MCP tools

Important: AEX only guards tool calls routed through it. aex gate covers Claude Code's built-in tools, aex proxy covers MCP tools. Use both for complete enforcement.

Built-in Tool Enforcement (aex gate)

aex gate is a Claude Code PreToolUse hook. It intercepts every tool call — including built-in tools like Read, Write, Edit, Bash, Glob, and Grep — and evaluates them against your AEX policy.

1. Create a policy

bash
aex init --policy

2. Configure the hook

Add to your .claude/settings.json:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": ".*",
        "command": "aex gate"
      }
    ]
  }
}

Tool name mapping

Claude Code uses PascalCase tool names. aex gate maps them to AEX capabilities:

Claude Code ToolAEX Capability
Readfile.read
Write, Edit, MultiEditfile.write
Glob, Grep, LSfile.read
Bashshell.exec
WebFetchnetwork.fetch
WebSearchnetwork.search
NotebookReadfile.read
NotebookEditfile.write
Agentagent.spawn

Write your policy using the AEX capability names:

aex
policy workspace v0

goal "Default security boundary for this repository."

allow file.read, file.write, tests.run, shell.exec
deny network.*, secrets.read

confirm before file.write

budget calls=100

With this policy, WebFetch and WebSearch are blocked (they map to network.*), Write requires confirmation (maps to file.write), and all calls are capped at 100.

How it works

For each tool call, Claude Code sends JSON on stdin:

json
{
  "session_id": "abc-123",
  "tool_name": "Write",
  "tool_input": { "file_path": "/src/foo.ts", "content": "..." }
}

aex gate responds with a permission decision:

  • Allow: { "permissionDecision": "allow" }
  • Deny: { "permissionDecision": "deny", "reason": "..." }
  • Confirm: { "permissionDecision": "ask", "message": "..." } — prompts the user

MCP Tool Enforcement (aex proxy)

For tools provided by MCP servers, use aex proxy to gate every call:

json
{
  "mcpServers": {
    "tools": {
      "command": "aex",
      "args": ["proxy", "--", "npx", "-y", "your-mcp-server"]
    }
  }
}

The proxy auto-discovers .aex/policy.aex. To also apply a task contract:

json
{
  "mcpServers": {
    "tools": {
      "command": "aex",
      "args": [
        "proxy",
        "--contract", "tasks/fix-test.aex",
        "--", "npx", "-y", "your-mcp-server"
      ]
    }
  }
}

The proxy filters tools/list responses, gates tools/call requests, enforces budgets and confirmations, and logs every decision as structured JSON to stderr.

Full Coverage Setup

For complete enforcement over both built-in and MCP tools:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": ".*",
        "command": "aex gate"
      }
    ]
  },
  "mcpServers": {
    "tools": {
      "command": "aex",
      "args": ["proxy", "--", "npx", "-y", "your-mcp-server"]
    }
  }
}

Preview effective permissions

bash
# Policy only
aex effective

# Policy + task contract
aex effective --contract tasks/fix-test.aex

aex proxy vs aex run

CommandPurposeWhen to use
aex gatePreToolUse hook — gates Claude Code's built-in toolsAlways-on policy enforcement
aex proxyMCP proxy — gates upstream MCP tool callsMCP server enforcement during interactive sessions
aex runExecute a specific task contract end-to-endOne-shot contract execution with full audit trail

aex gate and aex proxy are real-time enforcement layers. aex run parses a contract, runs each step, and exits.

Contract Mode

For tasks that modify code, use AEX's draft → review → run workflow instead of letting Claude freestyle:

Pattern 1: Draft with aex draft

bash
aex draft "fix the failing test in src/foo.ts" --model anthropic
aex review .aex/runs/20260502-fix-test.aex --run

Claude generates a constrained AEX task contract. You review the contract — tool permissions, checks, confirmations — then AEX executes it with full enforcement.

Pattern 2: Claude drafts, AEX runs

Ask Claude to write an AEX contract directly:

> Draft an AEX task contract that fixes the failing test in src/foo.ts

Save Claude's output to .aex/runs/fix-test.aex, then:

bash
aex check .aex/runs/fix-test.aex
aex review .aex/runs/fix-test.aex --run

When to use which mode

SituationMode
Exploring code, reading filesExploratory — policy via gate/proxy
Fixing bugs, writing codeContract — aex draft + aex review --run
Deploying, migratingContract with strict approvals

Key principle: The model drafts. The human reviews. AEX executes. Claude does not freestyle edits in contract mode.

Session Checkpoints

The proxy's meta-tools let Claude checkpoint and resume sessions across conversations.

Save Progress

Mid-session, Claude can save the current state:

Claude: calls aex.checkpoint({ name: "fix-auth", description: "Identified the bug" })

This writes the audit log, budget state, and tool call history to .aex/checkpoints/fix-auth/.

Resume Later

In a new session, Claude can resume:

Claude: calls aex.resume({ name: "fix-auth" })

The proxy restores the budget counter and returns the session context (what tools were called, what was accomplished) so Claude can continue without repeating work.

Discover Contracts

Claude can list available task contracts and checkpoints:

Claude: calls aex.list_tasks()
Claude: calls aex.review_task({ task: "tasks/fix-test.aex" })

Meta-tools are automatically available when using aex proxy. See the Meta-Tools Reference for full details.

Pairing with CLAUDE.md

AspectCLAUDE.mdAEX Policy
FormatFree-form markdownStructured DSL
EnforcementBest-effort (model follows instructions)Deterministic via aex gate / aex proxy
ScopeSession-wide guidanceSession-wide (policy) or per-task (contract)
AuditNo built-in loggingEvery tool call logged as structured JSON

Use both together: CLAUDE.md for project-wide conventions, AEX policy for enforcing permission boundaries.

Troubleshooting

Hook not firing

Verify your .claude/settings.json is valid JSON and the hook is under PreToolUse:

bash
cat .claude/settings.json | python3 -m json.tool

The matcher must be ".*" to catch all tools. Check that aex is on your PATH:

bash
which aex

Policy not found

aex gate fails closed by default — if no policy is found, all tool calls are denied. Create a policy with aex init --policy, or pass one explicitly:

json
{ "matcher": ".*", "command": "aex gate --policy path/to/policy.aex" }

To allow all calls when no policy exists (not recommended), pass --allow-no-policy:

json
{ "matcher": ".*", "command": "aex gate --allow-no-policy" }

Tool blocked unexpectedly

Check which AEX capability the tool maps to:

If blockedMaps toCheck your policy for
Bashshell.execallow shell.exec
WebFetchnetwork.fetchNot in deny network.*
Writefile.writeallow file.write, not deny file.*

Run aex effective to see the merged permissions:

bash
aex effective

Budget exhausted mid-session

The budget resets per session. If you're hitting limits, increase the budget in your policy:

aex
budget calls=200

Or use aex proxy with --auto-confirm for automated workflows where confirmation prompts would block:

bash
aex proxy --auto-confirm -- npx your-mcp-server

See Also

Prompts are not permissions. Plans are not contracts.