You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix Claude Code schema, add Cursor reference impl, verify live integration
Claude Code adapter:
- Corrected response shape to use hookSpecificOutput.permissionDecision
(per https://code.claude.com/docs/en/hooks); previous version emitted
a top-level {"decision": "block"} which Claude Code does not parse
on PreToolUse.
- Honor Claude Code's native ask/defer permissionDecision values
instead of substituting them to deny.
- PostToolUse: handle the actual tool_response object (stdout/stderr/...)
rather than the docs-claimed tool_output string. Preserve tool_use_id
and duration_ms for Guardian correlation.
- Added real_claude_code_payloads.example: captured payloads from a real
`claude --print` session for reference.
- Updated tests with corrected expectations (13 tests, all passing).
Live integration verified against a real `claude --print` session:
- ALLOW path: command ran, Guardian saw toolCallRequest + toolCallResult
with the real Claude Code session id.
- DENY path: Claude Code surfaced "The command was blocked - a policy
named '...' denied the Bash tool call, so it never ran." Reasoning
flowed from Guardian -> adapter -> Claude Code -> user-visible message.
Cursor adapter (NEW, promoted from research to reference implementation):
- Schema source: ~/.cursor/skills-cursor/create-hook/SKILL.md
- Full implementation: cursor_adapter.py covers all 20 documented Cursor
hook events.
- Per-event dispatch via argv[1] (Cursor wires one command per event).
- Per-event output translation:
- Permission events (preToolUse, subagentStart, beforeShell, beforeMCP)
use top-level {permission, user_message, agent_message, updated_input}
- Post-tool events use additional_context / updated_mcp_tool_output
- subagentStop uses followup_message
- beforeSubmitPrompt uses exit code 2 to block (Cursor's protocol)
- Reuses the Claude Code example Guardian (same ACS shape on the wire).
- 13 round-trip tests, all passing.
Example Guardian (shared):
- Recognize both 'Bash' and 'Shell' tool names (Cursor uses Shell).
- Allow subagentStart, knowledgeRetrieval, memoryStore,
memoryContextRetrieval in addition to the original allow-list.
Total: 26/26 tests passing across both adapters.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| Adapter | Status | Mapping | Working adapter | Tests | Live integration verified |
8
+
|---|---|---|---|---|---|
9
+
|[claude-code](./claude-code/)| Reference implementation | ✓ | ✓ | ✓ 13 round-trip tests| ✓ ALLOW + DENY paths verified against a real `claude --print` session|
10
+
|[cursor](./cursor/)|Reference implementation | ✓ |✓|✓ 13 round-trip tests | ⚠ Manual verification by reviewer with Cursor installed (Cursor has no headless mode)|
11
+
|[nat](./nat/)|Draft| ✓| —| — | — |
12
12
13
13
## The adapter pattern
14
14
15
15
Each adapter does the same three things, with framework-specific glue:
16
16
17
-
1. Listen for the framework's lifecycle events (hook callback, middleware, command-driven shell hook, etc.).
17
+
1. Listen for the framework's lifecycle events (hook callback, command-driven shell hook, middleware, etc.).
18
18
2. Translate each event to an ACS JSON-RPC request and send it to a Guardian endpoint over HTTP.
19
19
3. Translate the Guardian's ACS decision back to the framework's expected response shape (allow / block / modify / pause).
20
20
21
-
The translation tables live in each adapter's `mapping.md`. The translation code lives in each adapter's reference implementation (when available).
21
+
The translation tables live in each adapter's `mapping.md`. The translation code lives in each adapter's reference implementation. The Claude Code and Cursor adapters share the same example Guardian (`adapters/claude-code/example_guardian.py`) — the ACS shape on the wire is identical regardless of which framework emits the event.
22
22
23
23
## Why "adapters" and not first-class framework support
24
24
25
25
ACS-Core specifies what a hook event looks like on the wire and what the Guardian's decision looks like coming back. It deliberately does not dictate how a framework physically wires the interception in. That last part is implementation: an MCP proxy, a framework callback, a shell-script command, or a runtime monkey-patch. The choice depends on which boundary the framework already crosses (see the FAQ in `docs/topics/faq.md`, "How do I make my framework ACS-conformant?").
26
26
27
27
The adapters here demonstrate the boundary choice for each target framework. Other frameworks can ship their own adapters, or contribute new ones to this directory.
28
28
29
+
## Cross-adapter design summary
30
+
31
+
| Aspect | Claude Code | Cursor | NAT (draft) |
32
+
|---|---|---|---|
33
+
| Interception mechanism | Shell command per hook (settings.json) | Shell command per hook (hooks.json) | Config-level middleware (planned) |
34
+
| Event dispatch | Event name in stdin JSON | Event name as `argv[1]`| Middleware lifecycle position |
{"session_id":"befb9d53-a5c4-4024-95e4-09c5b1d08b17","transcript_path":"/Users/barkaduri/.claude/projects/-private-tmp-acs-real-test/befb9d53-a5c4-4024-95e4-09c5b1d08b17.jsonl","cwd":"/private/tmp/acs-real-test","permission_mode":"acceptEdits","hook_event_name":"UserPromptSubmit","prompt":"Run the shell command: echo HOOK_PROBE_OK"}
7
+
8
+
=== END ===
9
+
=== HOOK FIRED 2026-06-15T08:55:56Z ===
10
+
{"session_id":"befb9d53-a5c4-4024-95e4-09c5b1d08b17","transcript_path":"/Users/barkaduri/.claude/projects/-private-tmp-acs-real-test/befb9d53-a5c4-4024-95e4-09c5b1d08b17.jsonl","cwd":"/private/tmp/acs-real-test","permission_mode":"acceptEdits","effort":{"level":"high"},"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"echo HOOK_PROBE_OK","description":"Echo a probe string"},"tool_use_id":"toolu_01VA6o3dtCvr716MhHFrA7VW"}
11
+
12
+
=== END ===
13
+
=== HOOK FIRED 2026-06-15T08:56:01Z ===
14
+
{"session_id":"befb9d53-a5c4-4024-95e4-09c5b1d08b17","transcript_path":"/Users/barkaduri/.claude/projects/-private-tmp-acs-real-test/befb9d53-a5c4-4024-95e4-09c5b1d08b17.jsonl","cwd":"/private/tmp/acs-real-test","permission_mode":"acceptEdits","effort":{"level":"high"},"hook_event_name":"PostToolUse","tool_name":"Bash","tool_input":{"command":"echo HOOK_PROBE_OK","description":"Echo a probe string"},"tool_response":{"stdout":"HOOK_PROBE_OK","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false},"tool_use_id":"toolu_01VA6o3dtCvr716MhHFrA7VW","duration_ms":5616}
0 commit comments